feat(explore): U4+U13 del 3 - multi-base er en PARTISJON, ikke en videre signatur (ORDRE 20260825T080753Z) [skip-docs]
Ordrens premiss («endrer run_project sin signatur») er FALSIFISERT foer bygging, og ordren ba selv om nettopp den sjekken. run_project kan ikke ta mer enn en bundle_dir: paa bundle-stien avleder den FIRE enkeltverdier fra DEN basen - prosjektet (_project_from_bundle fail-faster naar basens egen project_id ikke er den forespurte), validatorens stage-0-baseline, agentenes lesekontekst og ExpeL-noekkelen - og returnerer ETT stemplet RunResult. En andre katalog paa den signaturen ville tvunget et stille velg-en for alle fire. Planens egen setning sier det samme lest naert: «pipelinen kjoeres per bundle som i dag (run_portfolio-formen)» = N kall, ikke ett kall med N. Levert form, tre soemmer: - mandate.Approach.bundle_id (default "" - hvert mandat skrevet foer i dag er fortsatt gyldig og dispatchbart uendret) - mandate.route_by_bundle - ren partisjon, fail-fast paa et mandat som ikke kan utfoeres som skrevet (load_mandate-regelen). En base som ingen approach navngir kjoeres ikke; med NOEYAKTIG en base absorberer den alt uten navn, som ikke er en gjetning men det eneste mulige svaret. - run.run_mandate_across_bundles - dispatchen. INGEN project_id-parameter: hver base sitt prosjekt leses fra DEN basens egen IR-projeksjon, altsaa den verdien _project_from_bundle allerede fail-faster mot. En delt VerdictStore traades paa tvers (kryss-base-laering, run_portfolio-formen), og med portfolio_meter gjelder de to S3.4-tennene som HAR mening her: oppstartsnekt (BudgetRefused) og aldri-startet + budget_stop + not_evaluated-rader. INGEN eksisterende kaller endrer signatur - CLI, hosting og simulation sender fortsatt en base hver, og kan fortsatt gjoere det. explore(): hver myntet approach baerer bundle_id; markoeren kan navngi basen; en umerket markoer med flere baser NEKTES (HypothesisParseError), med en base resolveres den. Froe-approaches VALIDERES men skrives ALDRI om (§ C.6 doer 1 er en bevaringsregel) - og valideringen skjer FOER foerste modellkall, samme oekt-57-hoist-grunn: ved unntaket alene ser en nekt etter forbruket identisk ut. explore()-docstringens gamle aerlighets-grense («venter paa at run_project tar mer enn en bundle_dir») er RETTET - den ville vaert usann fra i dag. Load-bearing MAALT, 12 mutasjoner alle roede mot HELE suiten, groenn kontroll 1020/5 (fra 999/5 + 21 nye tester); golden demo-transcript.stdout BYTE-UENDRET (ea8c534773acdbe41ae68f2c55724d69aaf8be4f): detach myntet bundle_id (2) · stille gjennomfall ved >1 base (1) · ukjent id resolvert etter rekkefoelge (1) · froe-sjekk etter forbruket (2) · ruteren gjetter foerste base (1) · uroutbar approach droppet (1) · dispatchen kollapser til en base (5) · project_id fra foerste base (2) · detach aldri-startet-tannen (1) · unreached urapportert (1) · fersk store per base (1) · detach oppstartsnekten (2). EN MUTASJON FALSIFISERTE TESTEN FOERST (repoets vakuoes-gate-klasse, aattende gang): store-testen sammenlignet med ==, og VerdictStore er en pydantic-modell med VERDI-likhet - tre ulike tomme stores er alle like, saa «fersk store per base» lot hele suiten staa groenn. Delt INSTANS er paastanden, saa testen asserterer naa paa `is`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L3YHobQC3WzYVoxSgZus4d
This commit is contained in:
parent
3cbea91a72
commit
18af86e422
6 changed files with 1051 additions and 17 deletions
|
|
@ -703,9 +703,46 @@ class HypothesisParseError(ExplorationError):
|
|||
"""
|
||||
|
||||
|
||||
def _parse_hypotheses(texts: Sequence[str]) -> list[tuple[str, str]]:
|
||||
"""Every marked ``(label, rationale)`` pair the hypothesiser committed to, in turn order."""
|
||||
found: list[tuple[str, str]] = []
|
||||
def _resolve_hypothesis_bundle(raw: str, bundle_ids: Sequence[str]) -> str:
|
||||
"""Which knowledge base a marked hypothesis belongs to (§ C.7), fail-closed.
|
||||
|
||||
Three rules, and the middle one is the whole reason the field can default at all:
|
||||
|
||||
* a stated id must be one that was CONFIGURED — refused by name, never resolved by position,
|
||||
exactly as ``_resolve_bundle`` refuses an unknown base for the read tools. A hypothesis that
|
||||
names a base must not be treated more leniently than a read of one.
|
||||
* a silent marker with at most one base configured resolves to that base (or to ``""`` with
|
||||
none). With one configured base there is no other value the field could take, so this is the
|
||||
only answer rather than a guess.
|
||||
* a silent marker with SEVERAL configured refuses. A marked line is a claim the loop committed
|
||||
to; one that cannot be routed is a claim it could not finish making, and the marker is
|
||||
precisely what makes that affordable to refuse — an unmarked turn is not a hypothesis, so
|
||||
there is no silence being turned into an error.
|
||||
"""
|
||||
if raw:
|
||||
if raw not in bundle_ids:
|
||||
known = ", ".join(bundle_ids) or "(none configured)"
|
||||
raise ExplorationError(
|
||||
f"hypothesis names knowledge base {raw!r}, which is not configured for this "
|
||||
f"exploration; configured: {known}"
|
||||
)
|
||||
return raw
|
||||
if len(bundle_ids) <= 1:
|
||||
return bundle_ids[0] if bundle_ids else ""
|
||||
raise HypothesisParseError(
|
||||
f"a marked hypothesis must name its knowledge base when several are configured "
|
||||
f'({", ".join(bundle_ids)}): add "bundle_id" to the marker payload. Routing it here '
|
||||
"would evaluate a direction against a project nobody asked about"
|
||||
)
|
||||
|
||||
|
||||
def _parse_hypotheses(
|
||||
texts: Sequence[str], bundle_ids: Sequence[str]
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Every marked ``(label, rationale, bundle_id)`` triple the hypothesiser committed to, in turn
|
||||
order. The base is resolved HERE rather than at minting time so an unroutable claim is refused
|
||||
while the line that made it is still in hand for the error message."""
|
||||
found: list[tuple[str, str, str]] = []
|
||||
for text in texts:
|
||||
for line in text.splitlines():
|
||||
stripped = line.strip()
|
||||
|
|
@ -724,12 +761,48 @@ def _parse_hypotheses(texts: Sequence[str]) -> list[tuple[str, str]]:
|
|||
"a marked hypothesis needs a non-empty 'label' and 'rationale'; got: "
|
||||
f"{stripped}"
|
||||
)
|
||||
found.append((str(data["label"]), str(data["rationale"])))
|
||||
found.append(
|
||||
(
|
||||
str(data["label"]),
|
||||
str(data["rationale"]),
|
||||
_resolve_hypothesis_bundle(str(data.get("bundle_id") or ""), bundle_ids),
|
||||
)
|
||||
)
|
||||
return found
|
||||
|
||||
|
||||
def refuse_unroutable_seeds(seeds: Sequence[Approach], bundle_ids: Sequence[str]) -> None:
|
||||
"""Refuse an expert's seed that could not be dispatched — BEFORE the first model call.
|
||||
|
||||
Validation, ALDRI reparasjon, and here the second half is the load-bearing one: a seed is the
|
||||
expert's own words and § C.6 door 1 is a PRESERVATION rule, so filling in a missing
|
||||
``bundle_id`` on their behalf would put their name on a routing decision they did not make.
|
||||
The single-base default belongs to ``route_by_bundle``, at consumption, where it is unambiguous
|
||||
by construction — never to a rewrite of the input.
|
||||
|
||||
Called before the loop starts rather than after it returns, and that placement is the økt-57
|
||||
outbox-hoist precedent: at the exception alone, a refusal after the exploration has spent its
|
||||
whole budget looks identical to one before it spent anything. The mandate is undispatchable
|
||||
either way; what is at stake is whether the expert pays to find that out.
|
||||
"""
|
||||
for seed in seeds:
|
||||
if seed.bundle_id:
|
||||
if seed.bundle_id not in bundle_ids:
|
||||
known = ", ".join(bundle_ids) or "(none configured)"
|
||||
raise ExplorationError(
|
||||
f"seed approach {seed.id!r} names knowledge base {seed.bundle_id!r}, which is "
|
||||
f"not configured for this exploration; configured: {known}"
|
||||
)
|
||||
elif len(bundle_ids) > 1:
|
||||
raise ExplorationError(
|
||||
f"seed approach {seed.id!r} names no knowledge base and {len(bundle_ids)} are "
|
||||
f"configured ({', '.join(bundle_ids)}); set Approach.bundle_id so the mandate can "
|
||||
"be dispatched to the base the hypothesis is actually about"
|
||||
)
|
||||
|
||||
|
||||
def _mint_approaches(
|
||||
seeds: Sequence[Approach], discovered: Sequence[tuple[str, str]]
|
||||
seeds: Sequence[Approach], discovered: Sequence[tuple[str, str, str]]
|
||||
) -> tuple[Approach, ...]:
|
||||
"""Seeds FIRST, untouched, then one approach per discovered direction.
|
||||
|
||||
|
|
@ -742,7 +815,7 @@ def _mint_approaches(
|
|||
taken = {approach.id for approach in seeds}
|
||||
minted: list[Approach] = list(seeds)
|
||||
counter = 0
|
||||
for label, rationale in discovered:
|
||||
for label, rationale, bundle_id in discovered:
|
||||
counter += 1
|
||||
candidate = f"hypothesis-{counter}"
|
||||
while candidate in taken or candidate == OWN_PROPOSAL_ID:
|
||||
|
|
@ -752,7 +825,12 @@ def _mint_approaches(
|
|||
# description is the hypothesiser's own words, VERBATIM: ``generate._build_messages`` feeds
|
||||
# an Approach.description to the proposer unchanged, and the reason a direction is worth
|
||||
# trying is exactly the half a model cannot re-derive from the cost table.
|
||||
minted.append(Approach(id=candidate, label=label, description=rationale))
|
||||
#
|
||||
# ``bundle_id`` is stamped on a MINTED approach because there is nothing here to preserve —
|
||||
# the opposite call from the seeds above, which pass through untouched (§ C.6 door 1).
|
||||
minted.append(
|
||||
Approach(id=candidate, label=label, description=rationale, bundle_id=bundle_id)
|
||||
)
|
||||
return tuple(minted)
|
||||
|
||||
|
||||
|
|
@ -792,13 +870,19 @@ async def explore(
|
|||
returned mandate whatever the loop found, including when the loop found nothing and including
|
||||
when it stopped early.
|
||||
|
||||
Honesty limits, stated rather than implied. (1) A run is bound to ONE mandate; multi-base
|
||||
dispatch (``Approach.bundle_id``, § C.7) waits for ``run_project`` to accept more than one
|
||||
``bundle_dir``, and shipping the field before its consumer would be a shape guessed instead of
|
||||
measured. (2) The ``quick_validate`` verdicts the hypothesiser saw are not in
|
||||
**Multi-base (§ C.7).** Every approach in the returned mandate carries ``bundle_id``, and
|
||||
``mandate.route_by_bundle`` partitions the commission by it so ``run.run_mandate_across_bundles``
|
||||
can run the pipeline once per base. ``run_project`` itself still takes ONE ``bundle_dir``, and
|
||||
that is the measured shape rather than a step not yet taken: it derives the project, the
|
||||
validator's cost baseline, the read context and the ExpeL query key from THE bundle and returns
|
||||
one stamped ``RunResult``, so a second directory on that signature would force a silent
|
||||
pick-one for all four.
|
||||
|
||||
Honesty limits, stated rather than implied. (1) The ``quick_validate`` verdicts the
|
||||
hypothesiser saw are not in
|
||||
``ExplorationResult``: they are level-1 advisory, and their home is the
|
||||
``{run_id}-exploration.json`` artefact the CLI wiring writes — pass an ``ExplorationTrace`` to
|
||||
collect them. (3) The exploration roles resolve through ``resolve_model``'s ``default``
|
||||
collect them. (2) The exploration roles resolve through ``resolve_model``'s ``default``
|
||||
fallback unless an operator maps them explicitly.
|
||||
|
||||
``trace`` is the caller's accumulator and is the ONLY way to see what a run that RAISED
|
||||
|
|
@ -816,6 +900,12 @@ async def explore(
|
|||
"requested and the reviewer would never be called (refused, never silently ignored)"
|
||||
)
|
||||
|
||||
# The base ids, resolved ONCE and before anything is built: they are what a seed and a marked
|
||||
# hypothesis are both routed against, and ``_bundle_index`` is the one place that decides them
|
||||
# (a duplicate basename refuses here rather than letting the manager read A believing it read B).
|
||||
bundle_ids = tuple(_bundle_index(bundle_dirs))
|
||||
refuse_unroutable_seeds(seed_approaches, bundle_ids)
|
||||
|
||||
if meter is None:
|
||||
meter = TokenMeter(Budget(max_tokens=contract.max_tokens, max_rounds=contract.max_rounds))
|
||||
if client_factory is None:
|
||||
|
|
@ -912,7 +1002,9 @@ async def explore(
|
|||
if stop is None:
|
||||
stop = _classify_stop(ledger_log, replans=replans, contract=contract)
|
||||
|
||||
discovered = _parse_hypotheses(hypothesis_texts) if stop != "unknown_speaker" else []
|
||||
discovered = (
|
||||
_parse_hypotheses(hypothesis_texts, bundle_ids) if stop != "unknown_speaker" else []
|
||||
)
|
||||
return ExplorationResult(
|
||||
mandate=Mandate(
|
||||
objective=prompt,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue