fix(explore): utforskningen skal ikke brenne budsjettet paa en argv som ikke kan fullfoere (ORDRE 20260823T204216Z)
Tre review-funn fra oerkt 57, alle handtert. 1. DEFEKT, funnet i review og REPRODUSERT foer fiks: --explore --outbox-dir uten --run-id kjoerte HELE utforskningen og ble deretter nektet av run_project, som eier outbox-kontrakten og nekter paa sin FOERSTE setning - tidlig nok for enhver sti som fantes foer U4, men utforskningen kjoerer FORAN det kallet. Artefakt- skrivingen hoppet ogsaa over (den krever begge), saa ikke engang regnskapet over hva som ble brukt overlevde. Fiksen er en HOIST i utforskningsblokka, ikke en andre kopi av regelen - samme hoist main() alt gjoer for de paakrevde argumentene, av samme grunn. Testen asserterer at INGEN modellkall skjedde, ikke bare at rc er 1: ved exit-koden ser en nekt etter forbruket identisk ut. 2. VAKUOES ASSERT FJERNET (repoets egen klasse, snudd innover): scenarioets label_in_bundle ble beregnet av den SAMME variabelen vakten raiser paa, saa feltet kunne strukturelt kun vaere False og assertet kunne ikke feile mot noen implementasjon - mens docstringen kalte det en kausalitetskontroll «akkurat som marker_in_run_a_prompt». Feltet og assertet er borte; vakten ER kontrollen, og en alltid-sann gjentakelse av den ville bare gjort den ekte lettere aa avfeie. 3. MAALINGS-PAASTANDEN PRESISERT: M10 (fjern --explore fra portefoelje-partisjonen) ble re-maalt mot HELE suiten etter test-fiksen - foer sto den kun maalt med -k. Invarianten sa «seksten mutasjoner mot HELE suiten + groenn kontroll 998/5» og slo dermed sammen tre ulike kontroller; den oppgir naa alle fire (990/5, 996/5, 998/5, 999/5). En paastand om egen maaling som er upresis om sin egen nevner er premiss-vs-faktum-regelen vendt innover. Pluss en uttalt aerlighets-grense i invarianten: den HOSTEDE flaten gir ingen innsyn i hva som formet mandatet (ingen outbox, intet utforskningsfelt i _response_payload). Bevisst scope-grense, men uttalt, fordi flatens hele argument er at svaret er etterproevbart. Sytten mutasjoner totalt, alle roede mot HELE suiten. 999 passed / 5 skipped. Golden-transkriptet byte-uendret (ea8c534773acdbe41ae68f2c55724d69aaf8be4f). mypy + ruff rene. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRZhBJcxqTcqWyMW6hBttx
This commit is contained in:
parent
2d44c5e224
commit
3cbea91a72
4 changed files with 69 additions and 14 deletions
22
CLAUDE.md
22
CLAUDE.md
|
|
@ -871,15 +871,26 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
utforskningen kjørte eller ei — `simulate_learning_loop`s to-markør-vakt i demo-form. Manager-
|
utforskningen kjørte eller ei — `simulate_learning_loop`s to-markør-vakt i demo-form. Manager-
|
||||||
manuset nøkles på PROMPT-STADIET, ikke prosjekt-ID-en, og det er ikke et unntak fra
|
manuset nøkles på PROMPT-STADIET, ikke prosjekt-ID-en, og det er ikke et unntak fra
|
||||||
`scripted_proposer`-regelen: manageren får FEM ulike spørsmål og prosjekt-ID-en er konstant over
|
`scripted_proposer`-regelen: manageren får FEM ulike spørsmål og prosjekt-ID-en er konstant over
|
||||||
alle fem. Load-bearing MÅLT (`tests/test_explore_callsites_loadbearing.py`, 23 tester), seksten
|
alle fem. **`--outbox-dir` uten `--run-id` NEKTES i utforskningsblokka, og det er en HOIST — ikke
|
||||||
mutasjoner alle røde mot HELE suiten + grønn kontroll 998/5: detach sink-appenden (1) · andre liste
|
en andre kopi av regelen:** `run_project` eier outbox-kontrakten og nekter på sin FØRSTE setning,
|
||||||
|
tidlig nok for enhver sti som fantes før U4, men utforskningen kjører FORAN det kallet — uten
|
||||||
|
hoisten brukes hele utforskningsbudsjettet på modellkall før nekten, og artefakt-skrivingen hoppes
|
||||||
|
over, så ikke engang regnskapet over hva som ble brukt overlever. Funnet i review FØR commit;
|
||||||
|
testen asserterer at INGEN modellkall skjedde, ikke bare at rc er 1 — ved exit-koden ser en nekt
|
||||||
|
etter forbruket identisk ut. **Scenarioet har BEVISST intet `label_in_bundle`-felt:** vakten
|
||||||
|
raiser før et resultat finnes, så feltet kunne kun vært `False`, og en assert på det ville vært
|
||||||
|
grønn mot enhver implementasjon — vakten ER kontrollen, og en alltid-sann gjentakelse av den ville
|
||||||
|
bare gjort den ekte lettere å avfeie. Load-bearing MÅLT
|
||||||
|
(`tests/test_explore_callsites_loadbearing.py`, 24 tester), sytten mutasjoner alle røde mot HELE
|
||||||
|
suiten, hver mot kontrollen som gjaldt da (990/5 for CLI-en + sporet, 996/5 for hosting, 998/5 for
|
||||||
|
sim-scenarioet, 999/5 for de to siste): detach sink-appenden (1) · andre liste
|
||||||
for rundene (4) · detach `--mandate`-nekten (1) · detach `--explore-config`-nekten (1) · skriv
|
for rundene (4) · detach `--mandate`-nekten (1) · detach `--explore-config`-nekten (1) · skriv
|
||||||
artefaktet kun ved fullført kjøring (1) · detach CLI-ens `mandate=` (1) · slipp
|
artefaktet kun ved fullført kjøring (1) · detach CLI-ens `mandate=` (1) · slipp
|
||||||
`enable_plan_review` gjennom, CLI (1) · detach `--bundle-dir`-kravet, CLI (1) · detach
|
`enable_plan_review` gjennom, CLI (1) · detach `--bundle-dir`-kravet, CLI (1) · detach
|
||||||
`--live-dry-run`-nekten (1) · fjern `--explore` fra portefølje-partisjonen (1) · videresend de
|
`--live-dry-run`-nekten (1) · fjern `--explore` fra portefølje-partisjonen (1) · videresend de
|
||||||
konsumerte feltene (2) · detach hostings `mandate=` (1) · detach `enable_plan_review`-nekten,
|
konsumerte feltene (2) · detach hostings `mandate=` (1) · detach `enable_plan_review`-nekten,
|
||||||
hosting (1) · detach `bundle_dir`-kravet, hosting (1) · detach sim-scenarioets `mandate=` (1) ·
|
hosting (1) · detach `bundle_dir`-kravet, hosting (1) · detach sim-scenarioets `mandate=` (1) ·
|
||||||
detach vakuitets-vakten (1). **ÉN MUTASJON FALSIFISERTE TESTEN FØRST (repoets vakuøs-gate-klasse,
|
detach vakuitets-vakten (1) · detach outbox/run-id-hoisten (1). **ÉN MUTASJON FALSIFISERTE TESTEN FØRST (repoets vakuøs-gate-klasse,
|
||||||
syvende gang):** portefølje-testen asserterte kun at meldingen nevnte `--explore`, og sto GRØNN
|
syvende gang):** portefølje-testen asserterte kun at meldingen nevnte `--explore`, og sto GRØNN
|
||||||
uten partisjonen — kjøringen falt da gjennom til «`--explore` requires `--bundle-dir`», som nevner
|
uten partisjonen — kjøringen falt da gjennom til «`--explore` requires `--bundle-dir`», som nevner
|
||||||
`--explore` også. To nekter som deler en delstreng er «assert aldri på ordlyd to grener deler»,
|
`--explore` også. To nekter som deler en delstreng er «assert aldri på ordlyd to grener deler»,
|
||||||
|
|
@ -888,7 +899,10 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
`bundle_dir`; utforskningens egne modellkall er UANNONSERTE (annonseringens kontrakt er at en
|
`bundle_dir`; utforskningens egne modellkall er UANNONSERTE (annonseringens kontrakt er at en
|
||||||
KOMMISJON erklæres før arbeidet den bestiller, og før `explore()` returnerer finnes ingen —
|
KOMMISJON erklæres før arbeidet den bestiller, og før `explore()` returnerer finnes ingen —
|
||||||
`exploration_notice` dekker gapet i det sløyfa er ferdig); `BudgetExceeded` ut av `--explore`
|
`exploration_notice` dekker gapet i det sløyfa er ferdig); `BudgetExceeded` ut av `--explore`
|
||||||
tracebacker som den gjør for debatten i dag.
|
tracebacker som den gjør for debatten i dag; og **den HOSTEDE flaten gir ingen innsyn i hva som
|
||||||
|
formet mandatet** — det er ingen outbox der og intet utforskningsfelt i `_response_payload`, så
|
||||||
|
ledgeren og de rådgivende dommene når kun CLI-ens artefakt. En bevisst scope-grense, men uttalt,
|
||||||
|
fordi flatens hele argument er at svaret er etterprøvbart.
|
||||||
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
|
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
|
||||||
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.
|
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1767,6 +1767,20 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
if args.outbox_dir and not args.run_id:
|
||||||
|
# A HOIST, not a second copy of the rule: ``run_project`` owns the outbox contract and
|
||||||
|
# refuses on its first statement, which is early enough for every path that existed
|
||||||
|
# before U4. The exploration runs AHEAD of that call, so without this the whole
|
||||||
|
# exploration budget is spent on model calls and only THEN refused — and the artefact
|
||||||
|
# write is skipped as well, so not even the record of what was spent survives. The same
|
||||||
|
# hoist ``main()`` performs for the required-args guard, for the same reason.
|
||||||
|
print(
|
||||||
|
"run refused: --outbox-dir requires --run-id, and with --explore that has to be "
|
||||||
|
"settled BEFORE the exploration runs (otherwise the loop spends its whole budget "
|
||||||
|
"on an argv that cannot finish)",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
exploration_contract: ExplorationContract | None = None
|
exploration_contract: ExplorationContract | None = None
|
||||||
if args.explore_config is not None:
|
if args.explore_config is not None:
|
||||||
|
|
|
||||||
|
|
@ -738,15 +738,16 @@ def scripted_exploration_factory(
|
||||||
class ExplorationSimulationResult:
|
class ExplorationSimulationResult:
|
||||||
"""The trace of one U4 walkthrough: what the loop shaped, and whether the pipeline used it.
|
"""The trace of one U4 walkthrough: what the loop shaped, and whether the pipeline used it.
|
||||||
|
|
||||||
``label_in_bundle`` is the causality control carried in the result, exactly as
|
There is deliberately NO ``label_in_bundle`` field. The vacuity guard below raises before a
|
||||||
``marker_in_run_a_prompt`` is: without it, a label the base already states would look like a
|
result exists, so such a field could only ever be ``False`` — an assertion on it would be green
|
||||||
shaped direction and the whole scenario would prove nothing."""
|
against every implementation, which is the class this scenario's guard exists to prevent. The
|
||||||
|
guard IS the control; a second, always-true restatement of it would only make the real one
|
||||||
|
easier to discount."""
|
||||||
|
|
||||||
exploration: ExplorationResult
|
exploration: ExplorationResult
|
||||||
trace: ExplorationTrace
|
trace: ExplorationTrace
|
||||||
run: RunResult
|
run: RunResult
|
||||||
label: str
|
label: str
|
||||||
label_in_bundle: bool
|
|
||||||
label_in_generation_prompt: bool
|
label_in_generation_prompt: bool
|
||||||
generation_prompts: list[str]
|
generation_prompts: list[str]
|
||||||
|
|
||||||
|
|
@ -828,7 +829,6 @@ async def simulate_exploration(
|
||||||
trace=trace,
|
trace=trace,
|
||||||
run=run,
|
run=run,
|
||||||
label=label,
|
label=label,
|
||||||
label_in_bundle=label in context,
|
|
||||||
label_in_generation_prompt=any(label in p for p in prompts),
|
label_in_generation_prompt=any(label in p for p in prompts),
|
||||||
generation_prompts=prompts,
|
generation_prompts=prompts,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -130,16 +130,16 @@ def _factory(
|
||||||
|
|
||||||
def factory(role: str) -> BaseChatClient:
|
def factory(role: str) -> BaseChatClient:
|
||||||
if role == explore.MANAGER_ROLE:
|
if role == explore.MANAGER_ROLE:
|
||||||
return ScriptedChatClient(reply_selector=_manager_script(ledgers), role=role)
|
return ScriptedChatClient(sink=sink, reply_selector=_manager_script(ledgers), role=role)
|
||||||
if role == explore.HYPOTHESISER_ROLE:
|
if role == explore.HYPOTHESISER_ROLE:
|
||||||
replies = list(hypothesiser)
|
replies = list(hypothesiser)
|
||||||
|
|
||||||
def _hyp(_blob: str, _role: str) -> str:
|
def _hyp(_blob: str, _role: str) -> str:
|
||||||
return replies.pop(0) if replies else "nothing further."
|
return replies.pop(0) if replies else "nothing further."
|
||||||
|
|
||||||
return ScriptedChatClient(reply_selector=_hyp, role=role)
|
return ScriptedChatClient(sink=sink, reply_selector=_hyp, role=role)
|
||||||
if role == explore.NAVIGATOR_ROLE:
|
if role == explore.NAVIGATOR_ROLE:
|
||||||
return ScriptedChatClient("NAVIGATOR: index read.", role=role)
|
return ScriptedChatClient("NAVIGATOR: index read.", sink, role=role)
|
||||||
return ScriptedChatClient(fallback, sink, role=role)
|
return ScriptedChatClient(fallback, sink, role=role)
|
||||||
|
|
||||||
return factory
|
return factory
|
||||||
|
|
@ -403,19 +403,22 @@ def test_explore_belongs_to_single_project_mode(tmp_path, capsys) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def _explored_main(monkeypatch: pytest.MonkeyPatch) -> None:
|
def _explored_main(monkeypatch: pytest.MonkeyPatch) -> list[str]:
|
||||||
"""Inject the role-dispatching scripted factory into the seam ``main()`` resolves through.
|
"""Inject the role-dispatching scripted factory into the seam ``main()`` resolves through.
|
||||||
|
|
||||||
``main()`` passes no ``client_factory``, and ``explore()`` imports ``run._default_factory``
|
``main()`` passes no ``client_factory``, and ``explore()`` imports ``run._default_factory``
|
||||||
lazily at call time, so this ONE patch covers both the exploration and the pipeline it feeds —
|
lazily at call time, so this ONE patch covers both the exploration and the pipeline it feeds —
|
||||||
which is what makes the arm below end-to-end rather than a wiring spy.
|
which is what makes the arm below end-to-end rather than a wiring spy.
|
||||||
"""
|
"""
|
||||||
|
sink: list[str] = []
|
||||||
factory = _factory(
|
factory = _factory(
|
||||||
ledgers=[_ledger_json(satisfied=False), _ledger_json(satisfied=True)],
|
ledgers=[_ledger_json(satisfied=False), _ledger_json(satisfied=True)],
|
||||||
hypothesiser=[_hypothesis_line(_LABEL, "the index says the fittings are old")],
|
hypothesiser=[_hypothesis_line(_LABEL, "the index says the fittings are old")],
|
||||||
fallback=_ENERGY_REPLY,
|
fallback=_ENERGY_REPLY,
|
||||||
|
sink=sink,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr("portfolio_optimiser.run._default_factory", lambda profile: factory)
|
monkeypatch.setattr("portfolio_optimiser.run._default_factory", lambda profile: factory)
|
||||||
|
return sink
|
||||||
|
|
||||||
|
|
||||||
def test_the_shaped_mandate_reaches_the_pipeline(tmp_path, capsys, _explored_main) -> None:
|
def test_the_shaped_mandate_reaches_the_pipeline(tmp_path, capsys, _explored_main) -> None:
|
||||||
|
|
@ -705,7 +708,6 @@ async def test_the_demo_scenario_lets_a_shaped_direction_reach_the_hypothesis(tm
|
||||||
"""
|
"""
|
||||||
result = await simulation.simulate_exploration(str(_BUNDLE_DIR), str(tmp_path), max_rounds=3)
|
result = await simulation.simulate_exploration(str(_BUNDLE_DIR), str(tmp_path), max_rounds=3)
|
||||||
|
|
||||||
assert result.label_in_bundle is False, "the control the scenario refuses on"
|
|
||||||
assert [a.label for a in result.exploration.mandate.approaches] == [result.label]
|
assert [a.label for a in result.exploration.mandate.approaches] == [result.label]
|
||||||
assert result.label_in_generation_prompt, (
|
assert result.label_in_generation_prompt, (
|
||||||
"the shaped direction never reached the hypothesis prompt — the demo would show a mandate "
|
"the shaped direction never reached the hypothesis prompt — the demo would show a mandate "
|
||||||
|
|
@ -714,6 +716,31 @@ async def test_the_demo_scenario_lets_a_shaped_direction_reach_the_hypothesis(tm
|
||||||
assert result.trace.ledger, "the exploration recorded no rounds"
|
assert result.trace.ledger, "the exploration recorded no rounds"
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_outbox_without_a_run_id_is_refused_before_the_exploration_spends_anything(
|
||||||
|
tmp_path, _explored_main
|
||||||
|
) -> None:
|
||||||
|
"""T16: an argv that cannot finish is refused BEFORE the loop costs anything.
|
||||||
|
|
||||||
|
``run_project`` refuses ``outbox_dir`` without ``run_id`` at its very first statement, which is
|
||||||
|
early enough for every path that existed before U4. The exploration runs AHEAD of that call, so
|
||||||
|
without this guard the run spends its whole exploration budget on model calls and only then
|
||||||
|
refuses — and the artefact write is skipped too, so not even the evidence of what was spent
|
||||||
|
survives. Exactly the hoist ``main()`` already performs twice ("an incomplete argv is refused
|
||||||
|
BEFORE the honesty banner could claim a scripted run happened").
|
||||||
|
|
||||||
|
The assertion is that NO model call happened, not merely that rc is 1: a refusal that arrives
|
||||||
|
after the spend looks identical at the exit code.
|
||||||
|
|
||||||
|
Detach point: drop the guard from the exploration block → RED.
|
||||||
|
"""
|
||||||
|
rc = run.main(_base_argv(tmp_path) + ["--outbox-dir", str(tmp_path / "outbox")])
|
||||||
|
assert rc == 1
|
||||||
|
assert not _explored_main, (
|
||||||
|
"the exploration made model calls before the run was refused — the budget was spent on an "
|
||||||
|
"argv that could never finish"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_a_direction_the_base_already_states_is_refused_as_vacuous(tmp_path) -> None:
|
async def test_a_direction_the_base_already_states_is_refused_as_vacuous(tmp_path) -> None:
|
||||||
"""S2: a label the knowledge base ALREADY contains is refused, not demonstrated.
|
"""S2: a label the knowledge base ALREADY contains is refused, not demonstrated.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue