docs: multi-base som invariant + den ende-til-ende-sloeyfa som beviser den (ORDRE 20260825T080753Z)
CLAUDE.md-invarianten «Multi-base er en PARTISJON, aldri en videre run_project-signatur» baerer hele designet: den strukturelle grunnen til at run_project ikke KAN ta flere bundle_dir (fire enkeltverdier avledet fra DEN basen), de tre soemmene, hvorfor dispatchen ikke tar project_id, de tolv maalte mutasjonene, og de fire uttalte aerlighets-grensene - inkludert at CLI-en er BEVISST uroert (§ C.8 ber om ETT nytt kallsted i run.py, levert i 57; et repeterbart --bundle-dir er en NY operatoerflate og en egen beslutning). README faar multi-base-doeren beskrevet der --explore alt er beskrevet, med den samme nekten uttalt for en leser som ikke leser CLAUDE.md: en hypotese som ikke navngir noen base blir NEKTET naar flere er konfigurert, aldri rutet til en gjetning. T22 er ende-til-ende-vitnet, og det eneste stedet de tre soemmene moetes: prompt + TO baser -> hypotesiseren former to retninger og navngir hver sin base -> route_by_bundle partisjonerer -> hver base sin run_project svarer for SIN hypotese og ingen andres. Assertet paa COVERAGE-radene, ikke paa kall-argumenter, fordi det er rapporten en fagperson faktisk leser: en approach som havnet i feil base ville fortsatt sett evaluert ut. 1021 passed / 5 skipped; golden demo-transcript.stdout byte-uendret (ea8c534773acdbe41ae68f2c55724d69aaf8be4f); mypy/ruff rene. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L3YHobQC3WzYVoxSgZus4d
This commit is contained in:
parent
18af86e422
commit
785261f229
3 changed files with 127 additions and 0 deletions
52
CLAUDE.md
52
CLAUDE.md
|
|
@ -903,6 +903,58 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
|||
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.
|
||||
- **Multi-base er en PARTISJON, aldri en videre `run_project`-signatur (U4+U13 del 3, § C.7, økt 58):**
|
||||
planens § C.7 og økt 56s egen ærlighets-grense leste som om leveransen var «`run_project` tar mer
|
||||
enn én `bundle_dir`». **Den kan ikke det, og nekten er STRUKTURELL:** på bundle-stien avleder
|
||||
`run_project` FIRE enkeltverdier fra DEN basen — prosjektet (`_project_from_bundle`, som
|
||||
fail-faster når basens egen `validator-input.json` ikke navngir det forespurte prosjektet),
|
||||
validatorens stage-0-baseline (S4.0s hele poeng er at gaten er forankret i DETTE prosjektets
|
||||
kostlinjer), agentenes lesekontekst og ExpeL-nøkkelen — og returnerer ETT stemplet `RunResult`.
|
||||
En andre katalog på den signaturen ville tvunget et stille velg-en for alle fire, som er den
|
||||
gjettede-form-klassen repoet nekter. **Planens egen setning sier det samme lest nært:**
|
||||
«pipelinen kjøres per bundle som i dag (`run_portfolio`-formen)» = N kall, ikke ETT kall med N.
|
||||
Premisset ble felt FØR bygging; ordren ba selv om nettopp den sjekken. **Konsekvensen er at INGEN
|
||||
eksisterende kaller endrer signatur** — CLI, hosting og simulation sender fortsatt én base hver,
|
||||
og kan fortsatt gjøre det. Tre sømmer: (1) `mandate.Approach.bundle_id`, default `""`, så hvert
|
||||
mandat skrevet før i dag er fortsatt gyldig OG dispatchbart uendret; (2) `mandate.route_by_bundle`
|
||||
— ren partisjon i `bundle_ids`-rekkefølge (aldri i approach-rekkefølge: spend-ordenen er en
|
||||
egenskap ved hvordan kjøringen ble konfigurert, ikke ved hvordan en modell tilfeldigvis sekvenserte
|
||||
hypotesene), **fail-fast på et mandat som ikke kan utføres som skrevet** (`load_mandate`-regelen —
|
||||
en kjøring skal aldri gå videre på en stille degradert bestilling); (3)
|
||||
`run.run_mandate_across_bundles` — dispatchen. **Den tar INGEN `project_id`-parameter, og det er
|
||||
designet:** hver bases prosjekt leses fra DEN basens egen IR-projeksjon, altså nøyaktig verdien
|
||||
`_project_from_bundle` allerede fail-faster mot, så en kaller-oppgitt konstant kunne uansett bare
|
||||
vært riktig for én base av N — den eksisterende fail-fasten blir rutingsnøkkelen, og gjetningen
|
||||
forsvinner. Ett `VerdictStore` trådes på tvers (kryss-base-læring, `run_portfolio`-formen), og
|
||||
**delt INSTANS er påstanden — ikke lik verdi** (se vakuitets-funnet under). En base ingen approach
|
||||
navngir kjøres IKKE (en kjøring koster penger, og bestillingen ba om ingenting der); med NØYAKTIG
|
||||
én base absorberer den alt uten navn, som ikke er en gjetning men det eneste mulige svaret — og
|
||||
det er dét som holder hvert pre-multi-base-mandat dispatchbart. `explore()` stempler `bundle_id`
|
||||
på hver MYNTET approach, men **skriver ALDRI om et frø** (§ C.6 dør 1 er en bevaringsregel — å
|
||||
fylle inn feltet på ekspertens vegne ville satt deres navn på en rutingsbeslutning de ikke tok);
|
||||
frøene VALIDERES i stedet, **FØR første modellkall** (økt-57-hoisten: ved unntaket alene ser en
|
||||
nekt etter forbruket identisk ut med en før). En umerket markør med flere baser NEKTES
|
||||
(`HypothesisParseError`), med én base resolveres den. **Budsjett: de to S3.4-tennene som HAR
|
||||
mening her** — oppstartsnekt (`BudgetRefused`) og aldri-startet + `budget_stop` +
|
||||
`not_evaluated`-rader i `MultiBaseResult.unreached`; bølge-reservasjonen har ingen motpart, for
|
||||
dispatchen er SEKVENSIELL. **Ærlighets-grenser, uttalt:** en base som RAISER propagerer
|
||||
(collect-and-continue tilhører `run_portfolio`, der kalleren sendte inn en batch uavhengige
|
||||
prosjekter); uten `portfolio_meter` er taket antall rutede baser × `max_tokens`, hver kjøring
|
||||
bundet for seg; outboxen er IKKE wiret (N kjøringer trenger N `run_id`-er, og å mynte dem her
|
||||
ville defaultet en nøkkel repoet krever at en kaller oppgir); og **CLI-en er BEVISST urørt** —
|
||||
§ C.8 ber om ETT nytt kallsted i `run.py` (`--explore`, levert i 57), og et repeterbart
|
||||
`--bundle-dir` er en NY operatørflate, altså en egen beslutning. Load-bearing MÅLT
|
||||
(`tests/test_multibase_loadbearing.py`, 22 tester), tolv mutasjoner alle røde mot HELE suiten +
|
||||
grønn kontroll 1020/5 og golden `demo-transcript.stdout` BYTE-UENDRET
|
||||
(`ea8c534773acdbe41ae68f2c55724d69aaf8be4f`): detach myntet `bundle_id` (2 røde) · stille
|
||||
gjennomfall ved >1 base (1) · ukjent id resolvert etter rekkefølge (1) · frø-sjekk etter forbruket
|
||||
(2 — asserten er på at NULL modellkall skjedde, ikke på unntaket) · ruteren gjetter første base (1)
|
||||
· uroutbar approach droppet (1) · dispatchen kollapser til én base (5) · `project_id` fra første
|
||||
base (2) · detach aldri-startet-tannen (1) · `unreached` urapportert (1) · fersk store per base (1)
|
||||
· detach oppstartsnekten (2). **ÉN MUTASJON FALSIFISERTE TESTEN FØRST (repoets vakuøs-gate-klasse,
|
||||
åttende gang):** store-testen sammenlignet med `==`, og `VerdictStore` er en pydantic-modell med
|
||||
VERDI-likhet — tre ulike TOMME stores er alle like, så «fersk store per base» lot HELE suiten stå
|
||||
grønn. Delt instans er påstanden, så testen asserterer nå på `is`.
|
||||
- **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.
|
||||
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -443,6 +443,17 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
The hosted surface takes the same door as `explore_prompt` + `explore_contract` on
|
||||
`POST /invocations`.
|
||||
|
||||
**Several knowledge bases (library API).** An exploration may be given more than one base
|
||||
(`explore(..., bundle_dirs=[a, b])`). Each approach it shapes records which base it belongs to
|
||||
(`Approach.bundle_id`), and `run_mandate_across_bundles(mandate, bundle_dirs, ...)` then runs the
|
||||
pipeline **once per base** — the ordinary `run_project`, with that base's own sub-mandate, and
|
||||
with each run's project read from that base's own `validator-input.json`. `run_project` itself
|
||||
still takes one `bundle_dir`, deliberately: it derives the project, the validator's cost
|
||||
baseline, the agents' read context and the retrieval key from the base it is handed, so a second
|
||||
directory on that call would mean silently picking one of them. A hypothesis that names no base
|
||||
is refused when several are configured, rather than routed to a guess. The CLI's `--bundle-dir`
|
||||
stays single-valued; multi-base is a library door today.
|
||||
|
||||
`--semantic-retrieval` (S3.1) is an **opt-in** ranking change, **off by default**. Off, prior
|
||||
verdicts are ranked exactly as before: a structural score over the affected cost-code set,
|
||||
measure type and magnitude bucket, with surface text deliberately excluded. On, that score is
|
||||
|
|
|
|||
|
|
@ -667,3 +667,67 @@ async def test_one_store_is_threaded_across_every_base(
|
|||
|
||||
assert len(calls) == 2
|
||||
assert all(c["store"] is store for c in calls)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exploration_and_dispatch_close_the_loop_over_two_bases(tmp_path: Path) -> None:
|
||||
"""T22: the end-to-end witness for § C.7 — prompt + TWO bases -> mandate -> two pipelines.
|
||||
|
||||
Every test above pins one seam. This one is the only place the three meet: the hypothesiser
|
||||
shapes two directions and names a DIFFERENT base for each, ``route_by_bundle`` partitions them,
|
||||
and each base's own ``run_project`` answers for its own hypothesis and for nobody else's. Every
|
||||
reply is scripted, so what is shown is that the plumbing closes — never that a live model would
|
||||
shape either direction well (the demo's §1 honesty limit, unchanged).
|
||||
|
||||
Assertion is on the COVERAGE rows rather than on call arguments, because that is the report an
|
||||
expert actually reads: an approach that reached the wrong base would still appear evaluated.
|
||||
"""
|
||||
bases = []
|
||||
for src in (_BYGG, _TUNNEL):
|
||||
dst = tmp_path / src.name
|
||||
shutil.copytree(src, dst)
|
||||
bases.append(str(dst))
|
||||
|
||||
exploration = await explore.explore(
|
||||
_PROMPT,
|
||||
contract=_CONTRACT,
|
||||
bundle_dirs=tuple(bases),
|
||||
client_factory=_factory(
|
||||
ledgers=[
|
||||
_ledger_json(satisfied=False, speaker=explore.HYPOTHESISER_ROLE),
|
||||
_ledger_json(satisfied=True, speaker=explore.HYPOTHESISER_ROLE),
|
||||
],
|
||||
hypothesiser=[
|
||||
_hypothesis_line("Behovsstyrt lys", "fixtures are 1990s", "bygg-energi-mikro")
|
||||
+ "\n"
|
||||
+ _hypothesis_line("Nattsenking", "the tunnel runs lit all night", "tunnel-hauglia")
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
assert [a.bundle_id for a in exploration.mandate.approaches] == [
|
||||
"bygg-energi-mikro",
|
||||
"tunnel-hauglia",
|
||||
]
|
||||
|
||||
def factory(role: str) -> BaseChatClient:
|
||||
return ScriptedChatClient(
|
||||
reply_selector=lambda _blob, _role: _reply_for("ENERGI-TOTAL-EL", 300000, 1.0, 30_000),
|
||||
role=role,
|
||||
)
|
||||
|
||||
result = await run_mandate_across_bundles(
|
||||
exploration.mandate,
|
||||
tuple(bases),
|
||||
"local",
|
||||
verdict_input=_VERDICT_INPUT,
|
||||
store=VerdictStore(verdicts=[]),
|
||||
client_factory=factory,
|
||||
max_rounds=1,
|
||||
)
|
||||
|
||||
assert [r.bundle_id for r in result.runs] == ["bygg-energi-mikro", "tunnel-hauglia"]
|
||||
assert [{row.id for row in r.result.coverage} for r in result.runs] == [
|
||||
{"hypothesis-1", "own-proposal"},
|
||||
{"hypothesis-2", "own-proposal"},
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue