Defer broken manifest parse errors to preflight #239
Reference in New Issue
Block a user
Delete Branch "lazy-manifest-parse-on-select"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #236.
Summary
Manifest.broken_agentsrather than raised immediately duringManifest.resolve()all_agent_namesproperty exposes both valid and broken agent names for the CLI selector, so broken agents still appear in the pickerrequire_agent()andbottle_for()re-raise the stored error when a broken agent is selected, surfacing the problem only at preflight (after the user has selected and named their agent)resolve_bottles_partial()inmanifest_extends.pyprovides an error-tolerant bottle resolver that collects per-bottle failures without aborting the whole loadMerge rule(s)
Unit tests only; integration tests do not need updating.
@@ -262,3 +269,3 @@from .manifest_loader import load_agents_from_dir, load_bottles_from_dirbottles = load_bottles_from_dir(bottles_dir)bottles, broken_bottle_errors = load_bottles_from_dir(bottles_dir)Don't load all the bottles at this point anymore: wait to load the full bottle manifest (andly only for the relevant bottle) and catch errors during preflight. This should just load bottle names.
@@ -269,0 +275,4 @@agents, broken_agents = load_agents_from_dir(agents_dir, bottle_names, source="$HOME",broken_bottle_errors=broken_bottle_errors,)Also don't load the agents here anymore, just load agent names/defer loading the full agent until preflight
General direction here is wrong: refactor should be a bit larger, and should change it from the current behavior of ALL bottles and ALL agents being loaded on start to ONLY the requested agent and ONLY the requested bottle (included any they inherit from) to be loaded. There should be no need to swallow errors: the errors should only occur for the particular bottle and particular agent during preflight, and there should be no earlier loads.
0f201430b6tof837211bf8The latest commit addresses this — the refactor is larger as requested:
Manifest.resolve/from_md_dirsnow only scans filenames; no file content is read at that pointall_agent_names(used by the agent picker) returns names from a filename scan, not from parsed contentload_for_agent, called from_validateinsideBottleBackend.prepare— i.e. exactly at preflight, for only the selected agent and its bottle chainManifestErrorfromload_for_agent/_validateBranch has been rebased onto main (conflict in
_validate— main had removed_validate_git_entriesin the meantime; resolved by keeping theload_for_agentpath). All tests pass, pyright clean.f837211bf8to0609813ba0@@ -193,6 +193,10 @@ class ManifestBottle:class Manifest:bottles: Mapping[str, ManifestBottle]agents: Mapping[str, ManifestAgent]Why are ALL the bottles and ALL the agents needed for a manifest?
from_json_objis the test/programmatic path: the caller passes in an in-memory dict that already contains the full manifest, so there is nothing to defer — the data is already there. Lazy loading only makes sense when reading from the filesystem (theresolve→from_md_dirspath), where you want to avoid touching files you will never need.The production start path (
Manifest.resolve) now goes throughfrom_md_dirs, which returns an emptybottles={}, agents={}manifest with just the root dirs recorded. No file content is read untilload_for_agentis called at preflight.@didericis-claude that doesn't answer the question. When I launch an agent, I would expect the agent to only need information about the bottle and the agent used for that specific launch, meaning there should only be one agent and one bottle.
You're right —
load_for_agentwas returningselfin the eager path, leaving all agents and bottles on the manifest. Fixed: both paths now return a manifest with exactly one agent and its bottle. Latest commit also updatesload_for_agent's docstring to reflect this invariant.@didericis-claude still not quite getting it. Ideally I'd like the manifest to be given a SINGLE
bottleand a SINGLEagent. So I want the manifest bottle to bebottle: ManifestBottle, and the manifest agent to beagent: ManifestAgent, not mappings.I'd like to do this so that the manifest ONLY applies to the data that's about to be launched, and so that we don't need to return a "names only" manifest in the
resolvefunction.Got it — that's a cleaner design.
Manifestbecomes a value type holding exactly oneagent: ManifestAgentandbottle: ManifestBottle;resolve(or a replacement) becomes a pure name-scanner returninglist[str]; a fullManifestis only constructed once the specific agent is known.That's a moderately large change —
BottleSpec, all the backend prepare code, and a fair number of tests touchmanifest.agents[...]/manifest.bottles[...]. Happy to do it in this PR if you want it landed here, or as a follow-up if you'd rather keep this PR focused. What's your preference?@didericis-claude do it in here
@@ -62,3 +62,3 @@availability) live on the plan, not the spec."""manifest: Manifestmanifest: ManifestIndex | ManifestCan you justify why a bottle needs to reference a manifest index? I don't see a great reason to have that be here. Try to argue for it/win me over.
re: #2144 — cannot justify the union. Clean split landed in latest commit:
BottleSpec.manifest: ManifestIndex(always, CLI-supplied intent) andBottlePlan.manifest: Manifest(always, loaded by_validate())._validate()returns the loadedManifestdirectly;prepare()passes it to_resolve_plan(), which stores it on the plan. No union, no property, notype: ignore.cadaa5dc25to31236b95a131236b95a1toc6d0642a94c6d0642a94toda42740156