feat(explore): katalogkallet koster O(baser), ikke O(korpus) (ORDRE 20260825T213645Z)

list_bundles returnerte hele rot-indeksens body for HVER konfigurert base samtidig,
pluss ett JSON-objekt per ufulgt kryss-lenke. Begge vokser med korpuset, saa prisen
paa aa finne ut HVILKE baser som finnes ble satt av hvor mye de INNEHOLDER - progressiv
disclosure snudd paa hodet.

Maalt med o200k_base, instrumentet foerst validert mot commons' egne fasittall:
  tre flate Vegnormal-baser   112 116 -> 362 tokens   (-99,7 %)
  171 grenbaser               124 942 -> 21 448       (-82,8 %)
Grenformen (vegnormal-okf 8145c23) lukket bundle-siden og gjorde katalogsiden verre,
noeyaktig som det repoet forutsa.

Et premiss ble felt FOER noe ble bygget paa det: "indeksbodyen forteller hva basen
handler om" er usant for maskin-importerte baser - grenbasenes index.md er en ren
lenkeliste uten frontmatter og prosa, saa feltet var dyrt OG innholdsloest der.

Fast vindu (200 tegn), aldri en andel av basen. Avkorting annonseres som FELT
(index_truncated), og en base som passer blir ikke merket avkortet. En ufulgt lenke
overlever som ANTALL; per-lenke-detaljen blir liggende der den er handlingsbar.
Hele indeksen er fortsatt ett read_file(id, "index.md") unna.

Taket (500 tegn/base) bor i TESTEN, ikke i explore.py.

Load-bearing MAALT: tests/test_catalogue_cost_loadbearing.py, 7 armer, ni mutasjoner
alle roede mot HELE suiten + groenn kontroll 1066/5 og golden demo-transcript.stdout
byte-uendret (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).

Ogsaa: MINOR-1 i syretest-rapporten rettet - flatheten er Doer C sin
(llm-ingestion-okf importer.py, §6-index-blokka), ikke vegnormals emitterform.
Verifisert mot kilden, ikke mot meldingen.

Maaling: docs/2026-08-26-katalogkostnaden.md
This commit is contained in:
Kjell Tore Guttormsen 2026-08-26 14:45:16 +02:00
commit a1f8522bdf
6 changed files with 421 additions and 11 deletions

View file

@ -689,6 +689,32 @@ def _resolve_bundle(index: Mapping[str, str], bundle_id: str) -> str:
return index[bundle_id]
#: Characters of the root index body one catalogue entry may carry. The catalogue's job is to let a
#: manager pick a base, not to read one, so the excerpt is a fixed-size window rather than a share
#: of the base: cost then scales with how many bases are configured, which the operator chose, and
#: not with how much they contain, which they did not. The number is deliberately small — a headline
#: and a first line or two — and the ceiling that guards it lives in the TEST, not here, because
#: raising this constant is the regression the gate exists to catch.
_CATALOGUE_EXCERPT_CHARS: Final = 200
def _index_excerpt(body: str) -> tuple[str, bool]:
"""A bounded VERBATIM prefix of an index body, plus whether anything was cut.
Cut at the last line break inside the budget, never mid-line: half a markdown link is a target a
model may well try to follow, and a path that never existed is worse than no path. A first line
longer than the whole budget has no break to cut at, and is cut hard the bound is the promise.
Returns the body UNTOUCHED and ``False`` when it already fits: an index that was never cut must
not be reported as cut (omission, never a lie in either direction ``cost_baseline_notice``'s
rule applied to a value instead of a line).
"""
if len(body) <= _CATALOGUE_EXCERPT_CHARS:
return body, False
cut = body.rfind("\n", 0, _CATALOGUE_EXCERPT_CHARS + 1)
return (body[:cut] if cut > 0 else body[:_CATALOGUE_EXCERPT_CHARS]), True
def navigator_tools(bundle_dirs: Sequence[str]) -> list[FunctionTool]:
"""The navigator's three tools: survey the catalogue, open one base, read one document.
@ -697,6 +723,22 @@ def navigator_tools(bundle_dirs: Sequence[str]) -> list[FunctionTool]:
without one cannot have its hypotheses reconciled against the project's real cost lines, and a
manager that does not know which bases are anchored cannot plan around it (§ C.7).
**The catalogue costs O(bases), never O(corpus)**, and that is the rung's whole point. It used
to return the WHOLE root index body per base plus one object per unfollowed cross-link both
corpus-sized so the cheapest rung of the ladder was the most expensive call in the loop:
112 116 o200k_base tokens over three flat Vegnormal bases, 124 942 over the 171 branch bases
that replaced them (measured 25.-26.08; ``tests/test_catalogue_cost_loadbearing.py``). Each
entry is now bounded by construction: a ``_CATALOGUE_EXCERPT_CHARS``-long VERBATIM prefix of the
index body plus counts. The full index stays exactly one ``read_file(id, "index.md")`` away, so
this is a disclosure level, not data loss.
**Truncation is ANNOUNCED, never silent** ``index_truncated`` is a field beside the excerpt,
not a marker glued into it, for the reason ``BudgetExceeded`` carries its triple as fields
(-(y)): a consumer that has to re-parse prose to learn whether it is holding the whole thing
has been handed a diagnostic it cannot act on. And an unfollowed cross-link keeps a COUNT here
(session 51's "a skip is tolerated but no longer silent") while its per-link detail stays where
it is actionable, on ``RunResult.skipped_links`` / ``DryRunReport.skipped_links``.
``read_bundle`` returns ``okf.bundle_context``, which EXCLUDES the ``type: verdict`` layer by
construction prior verdicts reach a hypothesis only through the gated ExpeL fold inside
``run_project``, never by being read as context here.
@ -706,28 +748,30 @@ def navigator_tools(bundle_dirs: Sequence[str]) -> list[FunctionTool]:
@tool(
name="list_bundles",
description=(
"List the knowledge bases available to this exploration: id, what the index says the "
"base is about, how many prior expert verdicts it holds, and whether it ships a cost "
"baseline (without one, numbers cannot be reconciled against the project's own)."
"List the knowledge bases available to this exploration: id, the OPENING of what the "
"index says, how many documents and prior expert verdicts the base holds, whether it "
"ships a cost baseline (without one, numbers cannot be reconciled against the "
"project's own), and how many cross-links could not be followed. The excerpt is cut "
"when index_truncated is true; read_file(id, 'index.md') gives the whole index."
),
)
def list_bundles() -> list[dict[str, Any]]:
catalogue: list[dict[str, Any]] = []
for bundle_id, bundle_dir in index.items():
bundle = okf.navigate_bundle(bundle_dir)
excerpt, truncated = _index_excerpt(bundle.index_summary)
catalogue.append(
{
"id": bundle_id,
"index_summary": bundle.index_summary,
"index_excerpt": excerpt,
"index_truncated": truncated,
"documents": len(bundle.context_files),
"verdict_count": len(bundle.verdicts),
# Tolerant on CONTENT, fail-fast on the PATH: an operator's bad directory is
# refused by navigate_bundle above, while a navigable base that simply has no
# baseline is legitimate (load_optional_cost_baseline's own contract).
"cost_baseline": okf.load_optional_cost_baseline(bundle_dir) is not None,
"skipped_links": [
{"from_file": s.from_file, "target": s.target, "reason": s.reason}
for s in bundle.skipped
],
"unreachable_links": len(bundle.skipped),
}
)
return catalogue