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 996a260a98
commit 3ccd09ed0d
8 changed files with 227 additions and 319 deletions
+14 -9
View File
@@ -37,7 +37,7 @@ import shlex
import sys
from abc import ABC, abstractmethod
from contextlib import AbstractContextManager
from dataclasses import dataclass
from dataclasses import dataclass, replace
from pathlib import Path
from typing import Any, Generic, Sequence, TypeVar
@@ -289,7 +289,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
write_launch_metadata,
)
self._validate(spec)
spec = self._validate(spec)
self._preflight()
@@ -355,16 +355,21 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
"""
pass
def _validate(self, spec: BottleSpec) -> None:
"""Cross-backend pre-launch checks. Confirms the agent exists
and the named skills are present on the host. Subclasses with
additional preconditions should override and call
`super()._validate(spec)` first."""
manifest = spec.manifest
manifest.require_agent(spec.agent_name)
def _validate(self, spec: BottleSpec) -> BottleSpec:
"""Cross-backend pre-launch checks. Parses the selected agent and
its bottle (raising ManifestError on invalid content), confirms
skills are present on the host, and every git IdentityFile resolves.
Returns a new BottleSpec whose manifest is fully loaded for the
selected agent. Subclasses with additional preconditions should
override and call `super()._validate(spec)` first, using the
returned spec for further checks."""
manifest = spec.manifest.load_for_agent(spec.agent_name)
spec = replace(spec, manifest=manifest)
agent = manifest.agents[spec.agent_name]
self._validate_skills(agent.skills)
self._validate_agent_provider_dockerfile(spec)
return spec
def _validate_skills(self, skills: Sequence[str]) -> None:
"""Each named skill must be a directory under the host's