feat(major2): prior_feedback is the third composable block of the hypothesis prompt [skip-docs]

Ordre 20260904T173146Z-8102814273-from-portfolio-optimiser, steg 2 av 10.

_build_messages faar prior_feedback (keyword-only, None => byte-identisk base-prompt,
samme kontrakt prior_rejection og approach alt oppgir). Ekspertens ORD, ordrett - aldri
forrige forslags JSON, av samme grunn prior_rejection kun baerer grunnen.

Blokken beskriver noe annet enn en avvisning: en kandidat validatoren AKSEPTERTE og et
menneske likevel ba om aa endre. Aa slaa dem sammen ville fortalt modellen at maskinen
protesterte da en person gjorde det.

Rekkefoelgen er fast: base -> approach-hode -> avvisning -> tilbakemelding. En prompt
kan lovlig baere BEGGE - det er forsoeket etter en revise hvis kjoepte forsoek validatoren
saa avviste: menneskets instruks STAAR til mennesket svarer neste gang, mens maskinens
grunn er per forsoek (kun den nyeste, som i dag).

RODT foer impl paa tre armer (TypeError: uventet keyword). Kontrollen (None => byte-identisk)
er halvdelen som holder hver eksisterende kjoering, golden og nav-fixtur uroert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-05 06:46:49 +02:00
commit d4c8691326
2 changed files with 92 additions and 3 deletions

View file

@ -279,6 +279,7 @@ def _build_messages(
prior_rejection: Rejection | None = None,
*,
approach: Approach | None = None,
prior_feedback: str | None = None,
) -> list[Message]:
"""Build the hypothesis prompt. When ``prior_rejection`` is set (Step 5, målbilde §5/§7),
append a revision block carrying ONLY the falsification *reason* verbatim never the prior
@ -294,8 +295,18 @@ def _build_messages(
exactly the part the model cannot infer from the cost data. ``None`` -> the byte-identical
base prompt, so an un-commissioned run is untouched (mirrors ``prior_rejection``).
The two are composable: a commissioned approach that the validator rejects is refined through
the SAME informed-refinement block, still bound to that approach.
When ``prior_feedback`` is set (MAJOR-2) a THIRD block carries a domain expert's own words
VERBATIM and only the words, never the previous proposal JSON, for exactly the reason
``prior_rejection`` carries only the reason. This block describes a candidate the validator
ACCEPTED and a human nonetheless asked to change, which is a different instruction from
"your numbers were refuted"; conflating the two would tell the model the machine objected
when a person did. ``None`` -> the byte-identical base prompt, like the other two.
All three are composable, and the ORDER is fixed: base -> approach head -> rejection ->
feedback. A prompt can legitimately carry a rejection AND a feedback at once that is the
attempt after a revise whose bought attempt the validator then rejected: the human's
instruction STANDS until the human next answers, while the machine's reason is per-attempt
(only the most recent, as today).
"""
if approach is None:
head = "Propose ONE concrete cost-saving measure for this project.\n"
@ -323,6 +334,13 @@ def _build_messages(
f"Reason: {prior_rejection.reason}\n"
"Produce a REVISED SavingsProposal that resolves this."
)
if prior_feedback is not None:
prompt += (
"\n\nA domain expert reviewed your previous proposal, which the deterministic "
"validator had accepted, and asked for a revision.\n"
f"Expert feedback: {prior_feedback}\n"
"Produce a REVISED SavingsProposal that follows this feedback."
)
return [Message(role="user", contents=[prompt])]