Skip to content

Commit 749cd76

Browse files
sriumcpclaude
andcommitted
fix(methodology): close all 5 subissues of tracker #225 (post-#218 follow-up)
Lands v1 scope of every methodology subissue revealed by the post-#218 paper-burst rerun (2026-05-27). Each subissue has a concrete fix that materially improves the rehearsal/real iteration loop and prepares the codebase for cross-run knowledge propagation. Closes #221 — Render iteration_mode + execute_mode_guidance in EXECUTE_ANALYZE prompt context. The DESIGN-phase agent (post-#212) honors rehearsal scope-shrink for probes, but the bundle it authors declares the full experimental design, and the EXECUTE_ANALYZE-phase agent (which had no mode signal) dutifully fanned out the full bundle anyway. New ``execute_mode_guidance_for(mode)`` returns rehearsal/real text distinct from the design-phase helper. Plumbed through ``_build_context`` for ``phase == "execute-analyze"``; rendered into ``execute_analyze.md`` + ``execute_analyze_thin.md`` with ``{{iteration_mode}}`` + ``{{mode_guidance}}`` placeholders. Test parametrized over ``with_claude_md`` (production thin path). Closes #222 — bundle.experiment_spec gains a structured ``rehearsal_subset`` field (seeds, arms, extra_validation_only). Schema-locked enum so a typo'd field name fails validation. The DESIGN methodology instructs agents to populate it when iter is rehearsal; EXECUTE_ANALYZE honors it (per #221's mode_guidance). Composes with #221: prose-only scope-shrink was unreliable; a structured field is enforceable. Closes #223 v1 — structured ``brief_amendments.jsonl`` schema + REPORT-context renderer. New ``brief_amendments.schema.json`` with required fields (``id, brief_section, problem, fix, priority``) and an enumerated priority. New ``_format_brief_amendments_summary(work_dir)`` helper renders amendments grouped by priority into the REPORT prompt. CLI ``nous brief apply-amendments`` deferred to v2. Closes #224 v1 — deterministic ``promote_gate.evaluate_promote_gate(work_dir, iteration) -> dict`` function. Pure Python; reads findings.json, brief_amendments.jsonl, applied_amendments.jsonl. Decision rule: missing/invalid findings → ``abort``; unapplied BLOCKING amendment → ``revise``; else → ``promote``. Engine state-machine integration (the actual halting behavior at iter boundaries) deferred to v2 — this PR lands the decision logic so it's testable in isolation before any engine state changes. Closes #226 — bundle.experiment_spec gains a structured ``timing_observations`` block (per-policy expected wall-time + recommended_turn_silence_threshold_seconds). ``SDKDispatcher.dispatch`` reads the prior iter's bundle for the recommended threshold and applies it as a per-call override; restores the campaign default after. Resolution chain: bundle override > campaign default > factory default (600s). Methodology prescribes that rehearsal-mode agents record per-policy timing observations during feasibility probes — the recurring ``externality-credit`` slowness across three reruns becomes structural data instead of folklore. Refs #225 (tracker — five children covered). Tests: +44 new (1133 passed, 1 skipped, 0 regressions). Behavioral throughout: assertions on resolved ctx values, on-disk artifacts, schema validation. Per CLAUDE.md "no live LLM calls" — all tests use existing seam-injected fakes. Compaction-safe plan at ``docs/plans/methodology-improvements-pr.md`` captures the full implementation map; memory entries at ``project_methodology_pr_in_flight.md`` and ``project_paper_burst_workload_divergence.md`` carry session context across compactions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ef6403e commit 749cd76

14 files changed

Lines changed: 1459 additions & 1 deletion

orchestrator/iteration_mode.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def iteration_mode_for(campaign: dict, iteration: int) -> Mode:
105105

106106

107107
def mode_guidance_for(mode: Mode) -> str:
108-
"""Return the prompt block that guides the agent for ``mode``.
108+
"""Return the DESIGN-phase prompt block that guides the agent for ``mode``.
109109
110110
Raises ``ValueError`` on an unknown mode value. Silently defaulting
111111
to REAL_GUIDANCE was the prior behavior; that's the more dangerous
@@ -119,3 +119,89 @@ def mode_guidance_for(mode: Mode) -> str:
119119
raise ValueError(
120120
f"unknown iteration mode {mode!r}; expected one of {VALID_MODES}"
121121
)
122+
123+
124+
# ─── Execute-phase mode guidance (#221) ──────────────────────────────────
125+
#
126+
# The DESIGN agent's mode_guidance shaped how it scope-shrunk probes /
127+
# bundle authoring. EXECUTE_ANALYZE needs its OWN mode-aware guidance
128+
# so it doesn't fan out the bundle at full scope when iter is rehearsal.
129+
# Without this, post-#212 paper-burst reruns observed the DESIGN agent
130+
# honoring rehearsal scope while EXECUTE_ANALYZE dutifully ran the full
131+
# 50-arm experiment anyway — defeating the cost asymmetry that was the
132+
# entire economic argument for #212.
133+
134+
EXECUTE_REHEARSAL_GUIDANCE = """\
135+
This iteration is in **REHEARSAL** mode (#212). The DESIGN agent's
136+
bundle declares the full experimental design (so iter-2 / future runs
137+
can run it untouched). YOUR JOB this iter:
138+
139+
1. **Honor the rehearsal scope.** If the bundle's
140+
``experiment_spec.rehearsal_subset`` is populated (#222), execute
141+
ONLY that subset (typically: 1 seed × the contrast-pair arms).
142+
Do NOT fan out the full ``experiment_spec`` — that's iter-2's job.
143+
If ``rehearsal_subset`` is missing, default to: first canonical
144+
seed + ``h-main`` and ``h-control-negative`` arms only.
145+
146+
2. **Validate the analysis pipeline.** Schema-pass at least one
147+
result through the analysis_summary.json computation. If the
148+
analysis script fails or returns null where data is present,
149+
fix the script (or surface the issue) before iter-2 runs.
150+
151+
3. **Append per-policy timing observations** (#226). During the
152+
feasibility / contrast-pair runs, measure wall-clock per policy.
153+
Record into ``experiment_spec.timing_observations``:
154+
``expected_wall_time_seconds_per_policy: { ea-wfq: 25, wfq: 23, ... }``
155+
and a derived ``recommended_turn_silence_threshold_seconds``
156+
(~3× the slowest observed policy + buffer). iter-2's watchdog
157+
reads these to calibrate.
158+
159+
4. **Emit ``brief_amendments.jsonl``** (post-#223 structured form) at
160+
``runs/iter-N/inputs/brief_amendments.jsonl`` if you find any
161+
campaign-spec friction (workload params, timing claims, missing
162+
flags, etc.). One JSON object per line; required fields: ``id``
163+
(pattern ``BA-N``), ``brief_section``, ``problem``, ``fix``,
164+
``priority`` (BLOCKING / HIGH / MEDIUM / LOW / INFO). Optional
165+
``evidence``, ``impact``.
166+
167+
5. **Append to ``bundle_amendments.jsonl``** (#211) when you override
168+
any parameter from ``experiment_spec.verified_parameters``.
169+
170+
6. **Write findings.json with ``mode: rehearsal``** in the outcome,
171+
noting that scientific claims are deferred to iter-2. The
172+
``experiment_valid: true`` flag means "the apparatus works" —
173+
not "the hypothesis is confirmed/refuted."
174+
175+
**Do NOT:**
176+
- Fan out the full bundle's seeds × policies grid.
177+
- Mark h-main as CONFIRMED / REFUTED based on rehearsal data.
178+
- Skip writing ``brief_amendments.jsonl`` if you discovered
179+
campaign-spec friction.
180+
"""
181+
182+
EXECUTE_REAL_GUIDANCE = """\
183+
This iteration is in **REAL** mode (#212). Run the full experiment_spec
184+
at the bundle's prescribed scope: all arms, full seed list. If a prior
185+
``rehearsal`` iter emitted ``brief_amendments.jsonl``, read it before
186+
launching the experiment — if any ``priority: BLOCKING`` amendments
187+
exist that haven't been applied to the brief, halt with a
188+
``failure_note.md`` (promotion-gate logic, #224). Otherwise run the
189+
full bundle, write ``findings.json`` with ``mode: real`` and a CONFIRMED /
190+
REFUTED / NULL status per arm, and append ``bundle_amendments.jsonl``
191+
(#211) for any parameter overrides observed during execution.
192+
"""
193+
194+
195+
def execute_mode_guidance_for(mode: Mode) -> str:
196+
"""Return the EXECUTE_ANALYZE-phase prompt block for ``mode`` (#221).
197+
198+
Distinct from ``mode_guidance_for`` (which targets the DESIGN agent).
199+
Raises ``ValueError`` on unknown modes for the same fail-loud reason.
200+
"""
201+
if mode == "rehearsal":
202+
return EXECUTE_REHEARSAL_GUIDANCE
203+
if mode == "real":
204+
return EXECUTE_REAL_GUIDANCE
205+
raise ValueError(
206+
f"unknown iteration mode {mode!r}; expected one of {VALID_MODES}"
207+
)

orchestrator/llm_dispatch.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,91 @@ def _format_results_summary(work_dir: Path) -> str:
133133
return "\n".join(lines)
134134

135135

136+
def _format_brief_amendments_summary(work_dir: Path) -> str:
137+
"""#223: surface structured ``brief_amendments.jsonl`` entries to
138+
the REPORT extractor.
139+
140+
Each amendment is a JSON object with required fields
141+
``id, brief_section, problem, fix, priority``. Optional
142+
``evidence``, ``impact``. The schema lives at
143+
``orchestrator/schemas/brief_amendments.schema.json``.
144+
145+
Walks ``runs/iter-*/inputs/brief_amendments.jsonl``, schema-validates
146+
rows individually (skipping malformed with a visible warning), and
147+
renders a per-iter listing grouped by priority. The REPORT extractor
148+
can use this to: (a) cite which amendments shaped the iteration's
149+
findings, (b) flag which BLOCKING amendments still need applying
150+
to the upstream brief (the cross-run learning loop).
151+
"""
152+
runs_dir = work_dir / "runs"
153+
if not runs_dir.is_dir():
154+
return "(no iteration directories — no brief amendments to report.)"
155+
iter_dirs = sorted(
156+
(d for d in runs_dir.iterdir()
157+
if d.is_dir() and d.name.startswith("iter-")),
158+
key=lambda d: d.name,
159+
)
160+
sections: list[str] = []
161+
total = 0
162+
for iter_dir in iter_dirs:
163+
log = iter_dir / "inputs" / "brief_amendments.jsonl"
164+
if not log.exists():
165+
continue
166+
try:
167+
text = log.read_text()
168+
except OSError as exc:
169+
sections.append(
170+
f"- {iter_dir.name}: brief_amendments.jsonl unreadable "
171+
f"({type(exc).__name__})"
172+
)
173+
continue
174+
rows: list[dict] = []
175+
skipped_malformed = 0
176+
for line in text.splitlines():
177+
if not line.strip():
178+
continue
179+
try:
180+
rows.append(json.loads(line))
181+
except json.JSONDecodeError:
182+
skipped_malformed += 1
183+
if not rows and skipped_malformed == 0:
184+
continue
185+
# Group by priority for at-a-glance triage. BLOCKING first, then
186+
# HIGH / MEDIUM / LOW / INFO. Unknown priorities sort last.
187+
priority_order = {
188+
"BLOCKING": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3, "INFO": 4,
189+
}
190+
rows_sorted = sorted(
191+
rows,
192+
key=lambda r: priority_order.get(
193+
str(r.get("priority", "")).upper(), 99
194+
),
195+
)
196+
header = f"- {iter_dir.name}: {len(rows)} amendment(s)"
197+
if skipped_malformed:
198+
header += f" + {skipped_malformed} malformed line(s) skipped"
199+
sections.append(header)
200+
total += len(rows)
201+
cap = 20
202+
for r in rows_sorted[:cap]:
203+
aid = r.get("id", "?")
204+
prio = r.get("priority", "?")
205+
section = r.get("brief_section", "?")
206+
problem = r.get("problem", "")
207+
sections.append(
208+
f" - [{prio}] {aid} (target: {section}) — "
209+
+ (problem[:160] + "..." if len(problem) > 160 else problem)
210+
)
211+
if len(rows_sorted) > cap:
212+
sections.append(f" - ... and {len(rows_sorted) - cap} more")
213+
if not sections:
214+
return (
215+
"(no brief_amendments.jsonl entries — the campaign brief was "
216+
"consistent with the agent's runs; no amendments queued.)"
217+
)
218+
return "\n".join(sections)
219+
220+
136221
def _format_bundle_amendments_summary(work_dir: Path) -> str:
137222
"""#211: surface bundle_amendments.jsonl entries to the REPORT extractor.
138223
@@ -594,6 +679,19 @@ def _build_context(
594679
"No design handoff available — explore the system directly."
595680
)
596681

682+
# #221: per-iteration mode signal in EXECUTE_ANALYZE too. The
683+
# post-#212 paper-burst rerun observed the DESIGN agent
684+
# honoring rehearsal scope-shrink while EXECUTE_ANALYZE
685+
# dutifully fanned out the full bundle anyway — because the
686+
# mode signal only flowed to DESIGN. Rendering it in execute
687+
# too closes that gap.
688+
from orchestrator.iteration_mode import (
689+
iteration_mode_for, execute_mode_guidance_for,
690+
)
691+
mode = iteration_mode_for(self.campaign, iteration)
692+
ctx["iteration_mode"] = mode
693+
ctx["mode_guidance"] = execute_mode_guidance_for(mode)
694+
597695
if perspective is not None:
598696
ctx["perspective_name"] = perspective
599697

@@ -656,6 +754,14 @@ def _build_context(
656754
ctx["bundle_amendments_summary"] = (
657755
_format_bundle_amendments_summary(self.work_dir)
658756
)
757+
# #223: structured brief_amendments — propagate to REPORT
758+
# so the extractor can cite which amendments shaped the
759+
# iteration's findings AND surface BLOCKING amendments
760+
# that haven't been applied to the upstream brief yet
761+
# (cross-run learning loop).
762+
ctx["brief_amendments_summary"] = (
763+
_format_brief_amendments_summary(self.work_dir)
764+
)
659765

660766
return ctx
661767

0 commit comments

Comments
 (0)