feat(p17b): ONE commission, SEVERAL bases -- reachable from the command line
``run_mandate_across_bundles`` has existed since session 58, reachable from FIVE
test files and from NO command line (measured: ``grep -n across-bundle run.py``
= 0 hits). ``--across-bundle <dir>``, repeated once per base, is that door.
The engine takes a CALLBACK rather than an outbox directory. Its own docstring
has always said N runs need N ``run_id``s and that minting them there would
default a key this repo requires a caller to supply -- so ``outbox_for`` is that
contract KEPT, not relaxed, and the operator-chosen ``<run-id>-<bundle_id>``
rule lives in ``main()`` where the decision was made. The order's alternative (a
caller running ``run_project`` itself over ``route_by_bundle``'s sub-mandates)
would be a second copy of the loop's id reconciliation, shared store, per-base
project resolution, collision accounting and both budget teeth.
``resolve_bundle_routing`` is ONE resolution shared by the engine and the
dry-run arm: a free trip answering with a different project id, or tolerating a
duplicate id the paid dispatch refuses, would rehearse a different run.
``{run-id}-multibase.json`` is written from a ``finally`` and every row is built
from the resolution plus disk, so the pass a cap cut short still leaves the
record. ``completed`` is a required field for ``ExplorationTrace.completed``'s
reason. ``stop_reason`` is read BACK from each base's own coverage artefact.
Load-bearing MEASURED (17 arms), four mutations all red against the WHOLE suite,
green control 1761/5 (from 1744/5, superset, 0 removed), golden byte-unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fc26d4f0a0
commit
5e4c497a84
5 changed files with 994 additions and 37 deletions
|
|
@ -230,6 +230,55 @@ def write_debate_tools(
|
|||
return path
|
||||
|
||||
|
||||
def write_multibase(
|
||||
outbox_dir: str,
|
||||
run_id: str,
|
||||
*,
|
||||
runs: Sequence[Mapping[str, Any]],
|
||||
completed: bool,
|
||||
unreached: Sequence[Mapping[str, Any]],
|
||||
collisions: Sequence[Mapping[str, Any]],
|
||||
stopped_early: bool,
|
||||
budget_stop: Mapping[str, Any] | None,
|
||||
) -> Path:
|
||||
"""Write ``{run_id}-multibase.json`` — what ONE commission did across SEVERAL bases (P17b).
|
||||
|
||||
The question no per-base artefact can answer. Each base writes its own full set under its own
|
||||
minted ``run_id``, but nothing in that set says in which ORDER the bases were spent, which id
|
||||
each one was given, which approaches were never reached, or which candidates two bases both
|
||||
described — and a reader who has to reconstruct the ``<run-id>-<bundle_id>`` convention to
|
||||
pair the files back to the pass has been handed a naming rule instead of a record.
|
||||
|
||||
Written IFF the pass was given an outbox, exactly like its neighbours, and the per-base
|
||||
``stop_reason`` rows are READ BACK from each base's own ``{run_id}-coverage.json`` by the
|
||||
caller rather than recomputed here: P19 D2 put that fact in that file, and a second derivation
|
||||
of it would be free to disagree with the one the judge reads.
|
||||
|
||||
Plain data only, so the RAW output layer stays MAF-free (``write_debate_tools``' own rule)."""
|
||||
directory = Path(outbox_dir)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
path = directory / f"{run_id}-multibase.json"
|
||||
path.write_text(
|
||||
_dump(
|
||||
{
|
||||
"run_id": run_id,
|
||||
# REQUIRED, never inferred from an empty ``unreached``: a pass a cap or a provider
|
||||
# cut short never got to say what it did not reach, and "nothing was left
|
||||
# unreached" must not be the value that means "we never found out"
|
||||
# (``ExplorationTrace.completed``'s own reason).
|
||||
"completed": completed,
|
||||
"runs": [dict(row) for row in runs],
|
||||
"unreached": [dict(row) for row in unreached],
|
||||
"collisions": [dict(row) for row in collisions],
|
||||
"stopped_early": stopped_early,
|
||||
"budget_stop": dict(budget_stop) if budget_stop is not None else None,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def write_prepass(
|
||||
outbox_dir: str,
|
||||
run_id: str,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import asyncio
|
|||
import json
|
||||
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
||||
from contextlib import AsyncExitStack
|
||||
from dataclasses import dataclass, replace
|
||||
from dataclasses import asdict, dataclass, replace
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
|
|
@ -2181,6 +2181,12 @@ class BundleRun:
|
|||
bundle_dir: str
|
||||
project_id: str
|
||||
result: RunResult
|
||||
#: The ``run_id`` this base's artefacts were written under, or ``""`` when the pass wrote no
|
||||
#: outbox at all. Carried here rather than re-derived by the caller for the reason the two
|
||||
#: fields above are: the CALLER minted it (P17b — N runs need N ids, and this engine refuses
|
||||
#: to default a key the repo requires a caller to supply), so a reader pairing an artefact
|
||||
#: back to a base must not have to reconstruct the naming convention to do it.
|
||||
run_id: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -2213,6 +2219,116 @@ class MultiBaseResult:
|
|||
collisions: tuple[VerdictCollision, ...] = ()
|
||||
|
||||
|
||||
def _coverage_stop_reason(outbox_dir: str, run_id: str) -> str:
|
||||
"""``BudgetExceeded.kind`` this base recorded, read back off its OWN coverage artefact.
|
||||
|
||||
Never recomputed from the ``RunResult``: P19 D2 put "why did this run stop" in
|
||||
``{run_id}-coverage.json`` precisely because ``_evaluate_mandate`` swallows the exception once
|
||||
something has been produced, so the dispatcher never sees it. A second derivation here would
|
||||
be free to disagree with the file the judge reads.
|
||||
|
||||
``"absent"`` is a THIRD value, and not the same as ``""``: a base that wrote no coverage file
|
||||
at all is a different finding from one that finished with nothing stopping it — the
|
||||
``stress`` judge's own vocabulary, reused rather than re-invented."""
|
||||
path = Path(outbox_dir) / f"{run_id}-coverage.json"
|
||||
if not path.is_file():
|
||||
return "absent"
|
||||
try:
|
||||
return str(json.loads(path.read_text(encoding="utf-8")).get("stop_reason", ""))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return "absent"
|
||||
|
||||
|
||||
def _write_multibase_summary(
|
||||
outbox_dir: str,
|
||||
run_id: str,
|
||||
*,
|
||||
resolved: Sequence[tuple[str, str, str]],
|
||||
mint: Callable[[str], tuple[str, str]],
|
||||
multi: MultiBaseResult | None,
|
||||
) -> None:
|
||||
"""Write ``{run_id}-multibase.json`` for a multi-base pass, COMPLETED or not (P17b).
|
||||
|
||||
Called from a ``finally``, which is ``write_parse_failures``' rule applied one layer up: the
|
||||
pass that most needs a record of what it spent is the one a cap or a provider cut short, and
|
||||
the engine's documented limit is that a base which RAISES propagates. Every per-base row is
|
||||
therefore built from the RESOLUTION and from DISK — the configured bases, the caller's own
|
||||
minting rule, and each base's own ``{run_id}-coverage.json`` — none of which needs the
|
||||
dispatch to have returned.
|
||||
|
||||
``completed`` is a REQUIRED field of the artefact and not an inference from an empty
|
||||
``unreached``: ``ExplorationTrace.completed``'s reason verbatim, because "nothing was left
|
||||
unreached" and "we never found out" must not be the same value. When the pass did not
|
||||
complete, ``unreached``/``collisions``/``budget_stop`` are what the dispatch never got to say,
|
||||
and they are written as empty/``None`` UNDER that flag rather than as findings.
|
||||
"""
|
||||
rows = []
|
||||
for bundle_id, bundle_dir, project_id in resolved:
|
||||
_, base_run_id = mint(bundle_id)
|
||||
rows.append(
|
||||
{
|
||||
"bundle_id": bundle_id,
|
||||
"bundle_dir": bundle_dir,
|
||||
"project_id": project_id,
|
||||
"run_id": base_run_id,
|
||||
"stop_reason": _coverage_stop_reason(outbox_dir, base_run_id),
|
||||
}
|
||||
)
|
||||
outbox.write_multibase(
|
||||
outbox_dir,
|
||||
run_id,
|
||||
runs=rows,
|
||||
completed=multi is not None,
|
||||
unreached=[asdict(row) for row in multi.unreached] if multi is not None else [],
|
||||
collisions=[asdict(row) for row in multi.collisions] if multi is not None else [],
|
||||
stopped_early=multi.stopped_early if multi is not None else False,
|
||||
budget_stop=(
|
||||
asdict(multi.budget_stop)
|
||||
if multi is not None and multi.budget_stop is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def resolve_bundle_routing(
|
||||
bundle_dirs: Sequence[str],
|
||||
) -> tuple[tuple[str, str, str], ...]:
|
||||
"""``(bundle_id, bundle_dir, project_id)`` per configured base, in CONFIGURED order.
|
||||
|
||||
The ONE resolution shared by ``run_mandate_across_bundles`` and the CLI's multi-base dry run
|
||||
(P17b). Extracted rather than copied for the reason the id derivation itself was unified in
|
||||
Step 10: a drill that answered with a different project id, or tolerated a duplicate id the
|
||||
paid dispatch refuses, would be a free trip that fails to measure the very run it precedes.
|
||||
|
||||
``bundle_id`` is ``okf.reconcile_bundle_id``'s — the declared id wins over the mount (S7a-3).
|
||||
Two bases answering to ONE id refuse here, the same refusal ``explore._bundle_index`` makes
|
||||
and for the same reason: the id is how the mandate NAMES a base, so a collision would let an
|
||||
approach be evaluated against A while the report says B (the S3.2 key-collision class).
|
||||
|
||||
``project_id`` is S7b søm 1's precedence, and it is load-bearing in BOTH directions: the
|
||||
hand-written IR projection FIRST (that file is what every existing base has always been routed
|
||||
by), the base's own DECLARED id as the fallback (an ingested corpus carries no projection, so
|
||||
file-only could not route it at all). No caller-supplied constant is admitted: it could only
|
||||
ever be right for one base out of N.
|
||||
|
||||
:raises MandateRoutingError: two configured bases share one id.
|
||||
"""
|
||||
out: list[tuple[str, str, str]] = []
|
||||
seen: dict[str, str] = {}
|
||||
for raw in bundle_dirs:
|
||||
bundle_id = okf.reconcile_bundle_id(raw).id
|
||||
if bundle_id in seen:
|
||||
raise MandateRoutingError(
|
||||
f"two knowledge bases share the id {bundle_id!r} ({seen[bundle_id]!r} and "
|
||||
f"{raw!r}); an approach names a base by that id, so it must be unique"
|
||||
)
|
||||
seen[bundle_id] = raw
|
||||
declared_ir = okf.load_optional_ir_projection(raw)
|
||||
project_id = str(declared_ir["project_id"]) if declared_ir is not None else bundle_id
|
||||
out.append((bundle_id, raw, project_id))
|
||||
return tuple(out)
|
||||
|
||||
|
||||
async def run_mandate_across_bundles(
|
||||
mandate: Mandate,
|
||||
bundle_dirs: Sequence[str],
|
||||
|
|
@ -2233,6 +2349,17 @@ async def run_mandate_across_bundles(
|
|||
#: AND ``project_id`` — each base's own, read by ``_project_from_bundle`` — so bases can be
|
||||
#: told apart even when a multi-base commission reuses approach ids.
|
||||
proposal_reviewer: ProposalReviewer | None = None,
|
||||
#: P17b. Where THIS base's artefacts go, and under which ``run_id`` — supplied by the caller
|
||||
#: per base, never minted here. That is the engine's own long-standing contract kept rather
|
||||
#: than relaxed: N runs need N ``run_id``s, and minting one here would default a key this repo
|
||||
#: requires a caller to supply, for byte-determinism. A CALLBACK rather than an
|
||||
#: ``outbox_dir``/``run_id`` pair because the naming rule is an OPERATOR decision
|
||||
#: (``<run-id>-<bundle_id>``, chosen 14.09) and belongs at the call site that made it; the
|
||||
#: alternative the order offered — a caller running ``run_project`` itself over
|
||||
#: ``route_by_bundle``'s sub-mandates — would be a SECOND copy of this loop's id
|
||||
#: reconciliation, shared store, per-base project resolution, collision accounting and both
|
||||
#: budget teeth (kø-(p), over five rules that each have exactly one home).
|
||||
outbox_for: Callable[[str], tuple[str, str]] | None = None,
|
||||
) -> MultiBaseResult:
|
||||
"""Evaluate ONE commission across SEVERAL knowledge bases — the multi-base dispatch (§ C.7).
|
||||
|
||||
|
|
@ -2267,30 +2394,18 @@ async def run_mandate_across_bundles(
|
|||
independent projects, whereas here the caller asked for ONE commission to be evaluated.
|
||||
(2) Without a ``portfolio_meter`` the pass's ceiling is the number of routed bases times
|
||||
``max_tokens``, each run bounded on its own — the global ledger is opt-in, and this does not
|
||||
re-implement it (``_run_meter`` is the one copy of the binding rule). (3) The outbox is NOT
|
||||
wired: N runs need N ``run_id``s, and minting them here would default a key this repo requires
|
||||
a caller to supply, for byte-determinism. A caller who needs artefacts per base calls
|
||||
``run_project`` itself with the sub-mandates ``route_by_bundle`` hands back.
|
||||
re-implement it (``_run_meter`` is the one copy of the binding rule). (3) The outbox is
|
||||
CALLER-KEYED: N runs need N ``run_id``s, and minting them here would default a key this repo
|
||||
requires a caller to supply, for byte-determinism — so ``outbox_for`` hands each base its
|
||||
directory and its id, and a caller that offers no callback still writes nothing (every
|
||||
pre-P17b call site, unchanged).
|
||||
|
||||
:raises MandateRoutingError: the commission cannot be routed against ``bundle_dirs``.
|
||||
:raises BudgetRefused: a global remainder that cannot fund a single run.
|
||||
"""
|
||||
by_id: dict[str, str] = {}
|
||||
for raw in bundle_dirs:
|
||||
# The ONE derivation rule (Step 10) — this used to be a second private copy of
|
||||
# ``Path(raw).name``, free to drift from ``explore``'s. The REFUSAL below stays local:
|
||||
# ``MandateRoutingError`` is this door's class, ``ExplorationError`` is explore's, and
|
||||
# unifying the derivation is not the same as unifying the two doors' error vocabularies.
|
||||
bundle_id = okf.reconcile_bundle_id(raw).id
|
||||
if bundle_id in by_id:
|
||||
# The same refusal ``explore._bundle_index`` makes, for the same reason: the id is how
|
||||
# the mandate names a base, so two bases answering to one name would let an approach be
|
||||
# evaluated against A while the report says B (the S3.2 key-collision class).
|
||||
raise MandateRoutingError(
|
||||
f"two knowledge bases share the id {bundle_id!r} ({by_id[bundle_id]!r} and "
|
||||
f"{raw!r}); an approach names a base by that id, so it must be unique"
|
||||
)
|
||||
by_id[bundle_id] = raw
|
||||
resolved = resolve_bundle_routing(bundle_dirs)
|
||||
by_id = {bundle_id: bundle_dir for bundle_id, bundle_dir, _ in resolved}
|
||||
project_by_id = {bundle_id: project_id for bundle_id, _, project_id in resolved}
|
||||
|
||||
routed = route_by_bundle(mandate, tuple(by_id))
|
||||
|
||||
|
|
@ -2327,19 +2442,7 @@ async def run_mandate_across_bundles(
|
|||
break
|
||||
|
||||
bundle_dir = by_id[bundle_id]
|
||||
# ONE reading of the base's own project id, used both to ADDRESS the run and to LABEL it.
|
||||
# A second lookup for the label would be the kø-(p) duplicate free to drift from the value
|
||||
# the run was actually dispatched with.
|
||||
#
|
||||
# S7b søm 1: the hand-written projection FIRST, the base's DECLARED id as the fallback. The
|
||||
# precedence is load-bearing in both directions. Declaration-first would re-address every
|
||||
# existing base whose ``project_id`` differs from its ``bundle_id`` — the file is what those
|
||||
# bases have always been routed by. File-only was the refusal this seam removes: an ingested
|
||||
# corpus carries no projection, so it could not be routed at all. ``bundle_id`` is the
|
||||
# identity every other door already resolves through (S7a-3), so the fallback introduces no
|
||||
# third notion of what a base is called; ``by_id`` above is that same resolution, reused.
|
||||
declared_ir = okf.load_optional_ir_projection(bundle_dir)
|
||||
project_id = str(declared_ir["project_id"]) if declared_ir is not None else bundle_id
|
||||
project_id = project_by_id[bundle_id]
|
||||
# D2: the id of the verdict THIS base minted, taken from ``run_project``'s existing
|
||||
# ``notify`` seam rather than off the returned ``RunResult``. ``notify`` fires inside the
|
||||
# capture block, so it is called exactly when a verdict exists (F2: never when nobody
|
||||
|
|
@ -2347,6 +2450,7 @@ async def run_mandate_across_bundles(
|
|||
# each iteration — a shared accumulator would let a later base read the previous base's
|
||||
# verdict and manufacture a collision that never happened.
|
||||
minted_here: list[str] = []
|
||||
base_outbox, base_run_id = outbox_for(bundle_id) if outbox_for is not None else (None, "")
|
||||
result = cast(
|
||||
RunResult,
|
||||
await run_project(
|
||||
|
|
@ -2354,6 +2458,8 @@ async def run_mandate_across_bundles(
|
|||
profile,
|
||||
docs_dir=bundle_dir,
|
||||
bundle_dir=bundle_dir,
|
||||
outbox_dir=base_outbox,
|
||||
run_id=base_run_id or None,
|
||||
notify=lambda verdict: minted_here.append(verdict.id),
|
||||
verdict_input=verdict_input,
|
||||
verdict_dir=verdict_dir,
|
||||
|
|
@ -2397,6 +2503,7 @@ async def run_mandate_across_bundles(
|
|||
bundle_dir=bundle_dir,
|
||||
project_id=project_id,
|
||||
result=result,
|
||||
run_id=base_run_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -2535,6 +2642,18 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser.add_argument(
|
||||
"--bundle-dir", default=None, help="OKF bundle dir (enables the Step-1 fold)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--across-bundle",
|
||||
action="append",
|
||||
default=None,
|
||||
metavar="DIR",
|
||||
help="P17b: run ONE commission across SEVERAL knowledge bases — repeat the flag once per "
|
||||
"base. The mandate is partitioned by each approach's bundle_id and the existing pipeline "
|
||||
"runs once per base, sequentially, threading ONE verdict store so a verdict minted against "
|
||||
"base k reaches base k+1. Each base writes its own artefact set under <run-id>-<bundle_id>, "
|
||||
"plus one <run-id>-multibase.json summary. Requires --mandate, --run-id and --outbox-dir; "
|
||||
"--bundle-dir stays ONE directory and is refused here",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verdict-dir",
|
||||
default=None,
|
||||
|
|
@ -2888,6 +3007,10 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"--goals": args.goals is not None,
|
||||
"--docs-dir": args.docs_dir is not None,
|
||||
"--bundle-dir": args.bundle_dir is not None,
|
||||
# P17b, and for its neighbours' reason: report mode returns ABOVE every dispatch,
|
||||
# including the multi-base one, so an omission here is a SILENT DROP of a whole pass
|
||||
# rather than a refusal (the F4 class).
|
||||
"--across-bundle": bool(args.across_bundle),
|
||||
"--verdict-dir": args.verdict_dir is not None,
|
||||
"--outbox-dir": args.outbox_dir is not None,
|
||||
"--run-id": args.run_id is not None,
|
||||
|
|
@ -2969,6 +3092,13 @@ def main(argv: list[str] | None = None) -> int:
|
|||
single_only = {
|
||||
"--docs-dir": args.docs_dir,
|
||||
"--bundle-dir": args.bundle_dir,
|
||||
# P17b. A portfolio pass keys on PROJECTS and reads each project's base off its own
|
||||
# row, so a run-level list of bases has nowhere to go; the two are different axes
|
||||
# (``MultiBaseResult`` is a distinct type from ``PortfolioResult`` for exactly that
|
||||
# reason). BY NAME, like its neighbours: falling through to "--across-bundle requires
|
||||
# --mandate" would tell an operator who wrote --portfolio --across-bundle to add a
|
||||
# flag that is legal in both modes, which answers the wrong question.
|
||||
"--across-bundle": bool(args.across_bundle),
|
||||
"--verdict-dir": args.verdict_dir,
|
||||
"--outbox-dir": args.outbox_dir,
|
||||
"--run-id": args.run_id,
|
||||
|
|
@ -3037,13 +3167,63 @@ def main(argv: list[str] | None = None) -> int:
|
|||
)
|
||||
return 1
|
||||
|
||||
# P17b — the multi-base door's own refusals, at FUNCTION level and never nested under another
|
||||
# flag's branch, for the F4 reason its neighbours are: under one, a bare combination falls
|
||||
# straight through to a dispatch that drops the flag in silence. Placed ABOVE the required-args
|
||||
# guard because this mode takes NO ``PROJECT_ID`` at all — each base's project is read from
|
||||
# THAT base's own IR projection, which is the whole reason the dispatch has no such parameter.
|
||||
if args.across_bundle:
|
||||
# The three things a multi-base pass cannot invent. ``--mandate`` because the commission IS
|
||||
# the partition key (without ``Approach.bundle_id`` there is nothing to route on), and the
|
||||
# outbox pair because N runs need N ``run_id``s: the engine refuses to default that key,
|
||||
# so the caller must supply the stem it mints them from.
|
||||
required = {
|
||||
"--mandate": args.mandate,
|
||||
"--run-id": args.run_id,
|
||||
"--outbox-dir": args.outbox_dir,
|
||||
}
|
||||
missing = [name for name, value in required.items() if not value]
|
||||
if missing:
|
||||
print(
|
||||
f"run refused: --across-bundle requires {', '.join(missing)} (the commission is "
|
||||
"what partitions the pass by base, and each base writes its own artefact set "
|
||||
"under <run-id>-<bundle_id> — a key this repo requires a caller to supply)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
# Four single-base modes, each refused BY NAME rather than by falling through. Every one
|
||||
# of them resolves something from THE base — one directory, one cut, one exploration, one
|
||||
# derived schedule — and silently picking which of N that means is the guessed-shape class
|
||||
# this repo refuses outright.
|
||||
conflicting = {
|
||||
"--bundle-dir": args.bundle_dir,
|
||||
"--explore": args.explore,
|
||||
"--prepass-payload": args.prepass_payload,
|
||||
"--proposals-from-mandate": args.proposals_from_mandate,
|
||||
}
|
||||
clash = [name for name, value in conflicting.items() if value]
|
||||
if clash:
|
||||
print(
|
||||
f"run refused: --across-bundle cannot be combined with {', '.join(clash)} (each "
|
||||
"of those resolves ONE knowledge base — a directory, a declared cut, an "
|
||||
"exploration or a derived schedule — and this mode configures several; which one "
|
||||
"was meant is not something this layer may decide)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
# Single-project mode requires PROJECT_ID + --docs-dir (compensating for the relaxed argparse
|
||||
# required/positional so the legacy contract keeps failing loudly via the refusal surface).
|
||||
# HOISTED above the scripted door (below) so an incomplete argv is refused BEFORE the honesty
|
||||
# banner could claim a scripted run happened; the refusal ORDER within single-project mode
|
||||
# (required args -> semantic-retrieval -> scripted) is unchanged.
|
||||
if not args.portfolio and (
|
||||
args.project_id is None or (args.docs_dir is None and args.bundle_dir is None)
|
||||
#
|
||||
# ``not args.across_bundle`` is a MODE test, not a relaxation: the multi-base pass takes no
|
||||
# PROJECT_ID and no single ``--bundle-dir``, and both of those are refused above by name.
|
||||
if (
|
||||
not args.portfolio
|
||||
and not args.across_bundle
|
||||
and (args.project_id is None or (args.docs_dir is None and args.bundle_dir is None))
|
||||
):
|
||||
print(
|
||||
"run refused: single-project mode requires PROJECT_ID and either --docs-dir or "
|
||||
|
|
@ -3892,6 +4072,158 @@ def main(argv: list[str] | None = None) -> int:
|
|||
print(settle(coverage))
|
||||
return 0
|
||||
|
||||
# P17b — the multi-base dispatch. Placed BELOW the announcement (the commission is declared
|
||||
# before the work it commissions) and ABOVE the portfolio dispatch, because it is a MODE
|
||||
# rather than a modifier: it runs the pass and returns. The dry-run arm lives INSIDE this
|
||||
# block rather than in the generic ``--live-dry-run`` branch further down, which addresses
|
||||
# ``args.project_id``/``args.bundle_dir`` — neither of which this argv has — and would
|
||||
# therefore drop the whole pass in silence (the F4 class).
|
||||
if args.across_bundle:
|
||||
assert mandate is not None # narrowed by the required-flags refusal above
|
||||
assert args.run_id is not None and args.outbox_dir is not None # same refusal
|
||||
bases = list(args.across_bundle)
|
||||
try:
|
||||
# Resolved and ROUTED on the free trip too: a commission that cannot be executed as
|
||||
# written must be refused while it is still free (the økt-57 hoist), and a drill that
|
||||
# tolerated a routing error the paid pass refuses would be a rehearsal of a different
|
||||
# run.
|
||||
resolved = resolve_bundle_routing(bases)
|
||||
route_by_bundle(mandate, tuple(bundle_id for bundle_id, _, _ in resolved))
|
||||
except (MandateRoutingError, okf.BundleIdMismatch, FileNotFoundError, ValueError) as exc:
|
||||
print(f"run refused: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if args.live_dry_run:
|
||||
for bundle_id, bundle_dir, project_id in resolved:
|
||||
try:
|
||||
report = asyncio.run(
|
||||
run_project(
|
||||
project_id,
|
||||
args.profile,
|
||||
docs_dir=bundle_dir,
|
||||
bundle_dir=bundle_dir,
|
||||
verdict_dir=args.verdict_dir,
|
||||
dimension=(
|
||||
load_dimension(args.dimension_config)
|
||||
if args.dimension_config
|
||||
else None
|
||||
),
|
||||
max_rounds=args.max_rounds,
|
||||
max_tokens=args.max_tokens,
|
||||
require_cost_baseline=args.require_cost_baseline,
|
||||
mcp_servers=mcp_servers,
|
||||
live_dry_run=True,
|
||||
)
|
||||
)
|
||||
except (ValueError, FileNotFoundError, ValidationError) as exc:
|
||||
print(f"live-dry-run refused: {bundle_id}: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
assert isinstance(report, DryRunReport)
|
||||
print(
|
||||
f"{bundle_id} ({project_id}): LIVE-DRY-RUN OK (profile={report.profile}, "
|
||||
f"models={report.resolved_models}, max_rounds={report.max_rounds}, "
|
||||
f"max_tokens={report.max_tokens}, top_k={report.top_k}) — "
|
||||
"ingen modellkall gjort (stoppet før første debate.run)"
|
||||
)
|
||||
# Every notice the single-base drill prints, per base: a pass whose SECOND base
|
||||
# cannot be anchored is exactly as unanchored as one whose first cannot, and a
|
||||
# drill that said it once would leave the operator guessing which base it meant.
|
||||
for notice in (
|
||||
cost_baseline_notice(report.cost_baseline_anchored),
|
||||
grounding_offer_notice(report.grounding_offer),
|
||||
skipped_links_notice(report.skipped_links),
|
||||
bundle_id_notice(report.bundle_id_source),
|
||||
):
|
||||
if notice is not None:
|
||||
print(notice)
|
||||
return 0
|
||||
|
||||
outbox_dir = args.outbox_dir
|
||||
stem = args.run_id
|
||||
|
||||
def outbox_for(bundle_id: str) -> tuple[str, str]:
|
||||
"""The OPERATOR-CHOSEN minting rule (14.09): ``<run-id>-<bundle_id>``, one stem per
|
||||
base. Written here, at the call site that made the decision, rather than inside the
|
||||
dispatch — which is precisely why the engine takes a callback and not a directory."""
|
||||
return outbox_dir, f"{stem}-{bundle_id}"
|
||||
|
||||
#: Bound by the dispatch when it RETURNS; still ``None`` when a base raised. The summary
|
||||
#: is written from a ``finally`` either way (``write_parse_failures``' precedent), because
|
||||
#: the pass that most needs a record of what it spent is the one a cap or a provider cut
|
||||
#: short — and the engine's documented limit is that a base which RAISES propagates.
|
||||
multi: MultiBaseResult | None = None
|
||||
try:
|
||||
multi = asyncio.run(
|
||||
run_mandate_across_bundles(
|
||||
mandate,
|
||||
tuple(bases),
|
||||
args.profile,
|
||||
verdict_dir=args.verdict_dir,
|
||||
dimension=(
|
||||
load_dimension(args.dimension_config) if args.dimension_config else None
|
||||
),
|
||||
client_factory=scripted_client_factory,
|
||||
max_rounds=args.max_rounds,
|
||||
max_tokens=args.max_tokens,
|
||||
verdict_input=_verdict_input_from_args(args),
|
||||
proposal_reviewer=(
|
||||
terminal_proposal_reviewer() if args.proposal_review else None
|
||||
),
|
||||
outbox_for=outbox_for,
|
||||
)
|
||||
)
|
||||
except ChatClientException as exc:
|
||||
print(f"run stopped: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
except ProposalReviewInputError as exc:
|
||||
print(f"run stopped: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
except (
|
||||
MandateRoutingError,
|
||||
okf.BundleIdMismatch,
|
||||
FileNotFoundError,
|
||||
ValidationError,
|
||||
ValueError,
|
||||
BudgetExceeded,
|
||||
) as exc:
|
||||
# ``BudgetExceeded`` is in the tuple for the single-project path's measured reason: it
|
||||
# is a ``RuntimeError``, and the FIRST approach to hit the cap re-raises by design
|
||||
# (``_evaluate_mandate`` only swallows it mid-list). Over several bases that is not an
|
||||
# edge case — round 3 measured ``stop_reason: rounds`` in 5 of 5 runs — so without this
|
||||
# arm the ordinary outcome of a multi-base pass is a traceback.
|
||||
print(f"run refused: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
finally:
|
||||
_write_multibase_summary(
|
||||
outbox_dir,
|
||||
args.run_id,
|
||||
resolved=resolved,
|
||||
mint=outbox_for,
|
||||
multi=multi,
|
||||
)
|
||||
|
||||
for bundle_run in multi.runs:
|
||||
print(f"--- {bundle_run.bundle_id} ({bundle_run.project_id}) ---")
|
||||
print(
|
||||
f"{bundle_run.project_id}: {type(bundle_run.result.outcome).__name__} "
|
||||
f"({verdict_notice(bundle_run.result)})"
|
||||
)
|
||||
# Every per-run notice the single-base path prints, read off THIS base's own stamp —
|
||||
# a pass that said them once could only be talking about one of N bases, and the
|
||||
# reader could not tell which.
|
||||
for notice in (
|
||||
cost_baseline_notice(bundle_run.result.provenance.cost_baseline_anchored),
|
||||
bundle_id_notice(bundle_run.result.provenance.bundle_id_source),
|
||||
skipped_links_notice(bundle_run.result.skipped_links),
|
||||
unkeyed_verdicts_notice(bundle_run.result.unkeyed_verdicts),
|
||||
):
|
||||
if notice is not None:
|
||||
print(notice)
|
||||
notice = collision_notice(multi.collisions)
|
||||
if notice is not None:
|
||||
print(notice)
|
||||
return 0
|
||||
|
||||
if args.portfolio:
|
||||
# Portfolio mode (Step 3): dispatch to the EXISTING run_portfolio via the fail-fast loaders
|
||||
# (run_portfolio itself is unchanged). Loader/ValueError failures surface through the same
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue