refactor: scan filenames at resolve, parse only selected agent at preflight

Manifest.resolve() now returns an empty-dict manifest with only directory
paths recorded (home_md, cwd_md). No content is read from any .md file
until load_for_agent() is called for a specific agent at preflight.

- Manifest.from_md_dirs: scan-only, no frontmatter parsing
- Manifest.load_for_agent: parses the selected agent file and its bottle
  chain; works on eager (from_json_obj) manifests too by returning self
- Manifest.all_agent_names: scans filenames in lazy mode
- backend._validate: calls load_for_agent and propagates upgraded spec
- cli/info.py, cli/list.py, cli/start.py: use load_for_agent / all_agent_names
- manifest_extends.py: reverted to original (no partial-resolve helpers)
- manifest_loader.py: only scan_agent_names + load_bottle_chain_from_dir
- Tests updated to call load_for_agent before accessing agents/bottles;
  test_md_agent_repos_deferred renamed to test_md_agent_repos_fails_at_preflight
This commit is contained in:
2026-06-20 02:45:33 +00:00
committed by didericis
parent 3b0738c928
commit db3c714027
8 changed files with 227 additions and 319 deletions
+1 -84
View File
@@ -4,8 +4,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from .manifest_util import ManifestError
if TYPE_CHECKING:
from .manifest import ManifestBottle
from .manifest_egress import ManifestEgressConfig
@@ -24,23 +22,6 @@ def resolve_bottles(raws: dict[str, dict[str, object]]) -> dict[str, ManifestBot
return cache
def resolve_bottles_partial(
raws: dict[str, dict[str, object]],
) -> tuple[dict[str, ManifestBottle], dict[str, ManifestError]]:
"""Apply `extends:` chains and return `(good, broken)`.
Bottles that fail validation (schema errors, bad extends, cycles) are
collected in `broken` rather than raising, so unrelated bottles remain
usable. Errors for parent bottles propagate to all children that extend
them."""
cache: dict[str, ManifestBottle] = {}
broken: dict[str, ManifestError] = {}
for name in raws:
if name not in cache and name not in broken:
_resolve_one_bottle_partial(name, raws, cache, broken, ())
return cache, broken
def _resolve_one_bottle(
name: str,
raws: dict[str, dict[str, object]],
@@ -48,7 +29,7 @@ def _resolve_one_bottle(
repos_cache: dict[str, dict[str, object]],
seen: tuple[str, ...],
) -> ManifestBottle:
from .manifest import ManifestBottle
from .manifest import ManifestBottle, ManifestError
if name in cache:
return cache[name]
@@ -229,67 +210,3 @@ def _merge_egress(
routes = parent.routes + child.routes
log = child.Log if "log" in child_egress_raw else parent.Log
return ManifestEgressConfig(routes=routes, Log=log)
def _resolve_one_bottle_partial(
name: str,
raws: dict[str, dict[str, object]],
cache: dict[str, ManifestBottle],
broken: dict[str, ManifestError],
seen: tuple[str, ...],
) -> None:
"""Error-tolerant variant: on failure, adds to `broken` instead of raising."""
from .manifest import ManifestBottle
if name in cache or name in broken:
return
if name in seen:
chain = " -> ".join(seen + (name,))
broken[name] = ManifestError(
f"bottle '{name}' is in an extends cycle: {chain}"
)
return
raw = raws[name]
parent_name_raw = raw.get("extends")
child_raw = {k: v for k, v in raw.items() if k != "extends"}
try:
if parent_name_raw is None:
cache[name] = ManifestBottle.from_dict(name, child_raw)
return
if not isinstance(parent_name_raw, str):
broken[name] = ManifestError(
f"bottle '{name}' extends must be a string "
f"(was {type(parent_name_raw).__name__})"
)
return
parent_name: str = parent_name_raw
if parent_name == name:
broken[name] = ManifestError(
f"bottle '{name}' extends itself; remove the self-reference"
)
return
if parent_name not in raws:
avail = ", ".join(sorted(raws.keys())) or "(none)"
broken[name] = ManifestError(
f"bottle '{name}' extends '{parent_name}' which is not "
f"defined. Available bottles: {avail}"
)
return
_resolve_one_bottle_partial(parent_name, raws, cache, broken, seen + (name,))
if parent_name in broken:
broken[name] = ManifestError(
f"bottle '{name}' extends '{parent_name}' which failed to load: "
f"{broken[parent_name]}"
)
return
parent = cache[parent_name]
cache[name] = _merge_bottles(parent, child_raw, name)
except ManifestError as exc:
broken[name] = exc