Cleanup backend and agent provider abstractions #216
Reference in New Issue
Block a user
Delete Branch "issue-215-dockerfile-colocation"
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 #215.
Summary
bot_bottle/contrib/claude/Dockerfileandbot_bottle/contrib/codex/Dockerfileso each provider owns its default image build inputs.AgentProvider.dockerfileandAgentProvider.guest_homeprovider-level defaults, removes Dockerfile paths fromAgentProviderRuntime, and falls back to each provider's bundled Dockerfile unless the manifest overrides it.AgentProvisionPlanas the source of truth for instance name, prompt file, image, Dockerfile path, and guest home across Docker and smolmachines bottle plans.bot_bottle/backend/resolve_common.py, renames backendprepare.pymodules toresolve_plan.py, and threads the resolved environment and launch slug through backend-specific resolution.--cwdworkspace provisioning throughBottleBackend.provision_workspaceagainst the running bottle for both Docker and smolmachines, without restoring the Docker derived-image workspace build path.bottle_state.pyto the top-levelbot_bottlepackage and updates imports/callers accordingly.Manifest*to distinguish manifest data from runtime/provider objects.@@ -134,0 +129,4 @@"""Path to the Claude provider Dockerfile. Resolved at call time sothe path is correct regardless of where this module is imported from."""# bot_bottle/backend/docker/ -> bot_bottle/ -> contrib/claude/Dockerfilereturn Path(__file__).resolve().parent.parent.parent / "contrib" / "claude" / "Dockerfile"Let's have the path be declared in the agent provider, and have it be a public attribute. Probably want to set it relative to file specifically for these inbuilt agent providers, but other agent providers could declare it elsewhere.
The other option would be to hardcode the agent provider to look for a dockerfile that sits next to the agent provider file in an agent provider contrib folder. What seems like the better option? Am thinking defaulting to a dockerfile that sits next to the agent provider file if no public attribute is set when declaring an agent provider probably makes sesne.
Agree that the convention-based default is the better option — providers that follow the layout (Dockerfile next to
agent_provider.py) get it for free without any boilerplate, and the attribute override is an escape hatch for the non-standard case.Concretely I am thinking:
dockerfileproperty to theAgentProviderbase class that returns the path of a siblingDockerfilefile (i.e.Path(inspect.getfile(type(self))).parent / "Dockerfile") when that file exists, elseNone.~/.bot-bottle/contrib/<template>/agent_provider.pywork the same way if they drop aDockerfilenext to the file._repo_dockerfile_path()incapability_apply.py(and the equivalent fallback inprepare.py) would delegate toprovider.dockerfileinstead of hardcoding.One question:
AgentProviderRuntimealready carries adockerfilefield that the built-in providers populate. ShouldAgentProvider.dockerfiledelegate toself.runtime.dockerfilefirst and fall back to the sibling-file convention? Or shouldruntime.dockerfilebe dropped in favour of the new property? Happy to proceed either way — just want to confirm before updating the implementation.@didericis-claude
runtime.dockerfileshould be dropped.Done in
963a178.runtime.dockerfileis dropped.AgentProvidernow has adockerfileproperty that returns the path of a siblingDockerfilefile (viainspect.getfile(type(self))), orNoneif no file exists. Built-in providers inherit it for free; custom user providers work the same way. All callers (docker/prepare.py,smolmachines/prepare.py,capability_apply.py) now callprovider_obj.dockerfiledirectly.@@ -113,4 +112,2 @@image_default = provider_runtime.imageelse:image_default = provider_runtime.imageimage = os.environ.get("BOT_BOTTLE_IMAGE", image_default)Are we using this anywhere? Not sure if it's worth keeping this. If it's being used should be renamed to
BOT_BOTTLE_AGENT_IMAGE.It is used — both
docker/prepare.pyandsmolmachines/prepare.pyreadBOT_BOTTLE_IMAGEas an env-var override that pins the agent image at launch time (useful in CI / integration tests to skip triggering a docker build). It's not referenced in tests, docs, or examples though, so it's an undocumented escape hatch.Agree
BOT_BOTTLE_AGENT_IMAGEis clearer (distinguishes it fromBOT_BOTTLE_DERIVED_IMAGEand any future image overrides). Happy to rename it in both prepare files as part of this PR — want me to go ahead?@didericis-claude no, remove it. Don't think we need it/overriding the image file seems unnecessary if we aren't using it in tests or in the wild. Can add it back if/when merited.
Done in
41590ed—BOT_BOTTLE_IMAGEremoved from bothdocker/prepare.pyandsmolmachines/prepare.py.@@ -128,2 +128,4 @@template."""@propertydef dockerfile(self) -> Path | None:This should always return a docker/should never return
NoneDone in
e0ecb7c—dockerfilenow returnsPathunconditionally; allis not Noneguards on the result removed from callers.@@ -111,4 +103,1 @@dockerfile_path = str(user_dockerfile)else:image_default = provider_runtime.imageelse:A provider should always have a dockerfile now, so this else shouldn't be necessary.
@@ -117,0 +107,4 @@else:image_default = f"bot-bottle-{provider.template}:{slug}"dockerfile_path = str(p_dockerfile)image = image_defaultThis rename used to make sense, but not anymore: places where we're assigning
image_defaultshould be changed so we assign toimageand we should just remove thisimage = image_defaultassignment.@@ -60,3 +60,3 @@bottle = manifest.bottle_for(spec.agent_name)provider = bottle.agent_providerprovider_runtime = runtime_for(provider.template)provider_obj = get_provider(provider.template)We shouldn't be doing this/we already have the provider object on line 61.
#1864 + #1866 — done in
a4e1285. Provider default is set first, overrides applied after;image_defaultis gone and so is the trailingelse.#1868 — need a clarification:
provider = bottle.agent_provideron line 61 is aManifestAgentProvider(the manifest data object — template name, optional dockerfile path, auth_token, etc.), not the pluginAgentProvider.get_provider(provider.template)is what gives us the plugin instance (.dockerfile,.runtime). Are you suggesting thatbottle.agent_providershould return the plugin directly, or something else?@didericis-claude we shouldn't have multiple classes with the same name... actual class name is
AgentProvider, even though it's a manifest. I thought it was the actualAgentProviderobject when looking in my IDE because it has the same name.Rename all of the manifest related class names to
ManifestX(soManifestAgentProvider,ManifestEgressRoute, etc) so it's clear which are just data classes and which are not.Got it. The conflicts are
manifest_agent.py:AgentProvidervsagent_provider.py:AgentProviderandmanifest_egress.py:EgressRoutevsegress.py:EgressRoute(plus any other manifest classes that shadow runtime names). The rename would touch the manifest files and all their importers across the codebase. Want me to do it in this PR, or open a separate one so this PR stays focused on the Dockerfile colocation work?@didericis-claude Do it in this PR.
On it.
Done in
b872985— all 11 manifest data classes renamed withManifestprefix across definition files and every usage site. 925 tests pass.Move agent Dockerfiles into contrib directoriesto Cleanup backend and agent provider abstractionsNoticed a lot of shared logic in
resolve_planthat should be in the base class even after prompting for consolidation. Going to look over the codebase and manually tidy up a bit.4dbe44e7ccto9470b8f955BottleBackend.prepare was calling resolve_manifest_dockerfile("", spec) for every bottle where the manifest did not set agent_provider.dockerfile. That resolves an empty string against user_cwd, returning the cwd itself — which docker then tried to read as a Dockerfile, giving "is a directory" errors during image build. When the manifest doesn't override, use the provider plugin's bundled Dockerfile path (next to its agent_provider.py module) — mirroring the pre-refactor behavior.3ceff1ac4fto626fe32896