PRD 0050: Move provider-specific agent logic into contrib #180
Reference in New Issue
Block a user
Delete Branch "prd-0050-agent-provider-contrib"
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 #177.
PRD: docs/prds/0050-agent-provider-contrib.md
Summary
bot_bottle/agent_provider.pyso Claude- and Codex-specific behavior lives underbot_bottle/contrib/claude/andbot_bottle/contrib/codex/, matching the contrib convention PRD 0048 established.provision_skills,provision_prompt, andprovision_supervise(renamedprovision_supervise_mcp) offBottleBackendand onto the provider plugin;provision_provider_authis folded into the per-provider provision step.~/.codex/config.tomlalongside Claude'sclaude mcp add.RE questions:
codex mcp addcommand@@ -0,0 +27,4 @@PROMPT_PATH = f"{GUEST_HOME}/.bot-bottle-prompt.txt"def apply_skills(plan: "BottlePlan", bottle: "Bottle") -> None:I don't think these different
applysteps should be separated like this... it seems like the responsibility of the agent provider to actually execute these different apply steps. They're likely to diverge based on harness changes, and the abstraction that allows both codex and claude to use them is weak. Ideally I think this whole file can go, and what's in here can happen in the different specific provision steps for the specific providers.Agreed — pushed
ceb8506559which deletes_provision_apply.pyentirely and inlines the skills / prompt / provision steps into each provider class.AgentProvidernow declares all four methods (provision_skills,provision_prompt,provision,provision_supervise_mcp) as abstract;ClaudeAgentProviderandCodexAgentProvidereach carry their own copy loop. Tests split intotest_contrib_claude_provider.pyandtest_contrib_codex_provider.py.@@ -27,6 +41,8 @@ PROVIDER_TEMPLATES = frozenset({PROVIDER_CLAUDE, PROVIDER_CODEX})CODEX_HOST_CREDENTIAL_HOSTS = ("api.openai.com", "chatgpt.com")PromptMode = Literal["append_file", "read_prompt_file"]GUEST_HOME = "/home/node"Don't have a default for this/have this be driven by the backend
@@ -40,5 +40,5 @@# (no TLS MITM) or its header DLP blocks the injected JWT.CODEX_HOST_CREDENTIAL_HOSTS = ("api.openai.com", "chatgpt.com")PromptMode = Literal["append_file", "read_prompt_file"]Done in
b2dc5fd20b—GUEST_HOMEis gone fromagent_provider.py,guest_homeis a required kwarg onprovision_plan(no default), and the_SKILLS_DIR/_PROMPT_PATHconstants in both contrib providers are gone too. The providers now derive in-guest paths fromplan.workspace_plan.guest_homeat call time, which the backend'spreparestep populates.@@ -60,3 +59,2 @@returncontainer_home = os.environ.get("BOT_BOTTLE_CONTAINER_HOME", "/home/node")container_gitconfig = f"{container_home}/.gitconfig"container_gitconfig = "/home/node/.gitconfig"/home/nodeshould be coming from the bottle plan... isn't this already there somewhere/don't we already need this to create the workspace? Regardless, it should be somewhere higher up in the plan.@@ -58,8 +57,7 @@ def _provision_git_gate_config(plan: DockerBottlePlan, bottle: Bottle) -> None:manifest_bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name)if not manifest_bottle.git:returnYou were right — workspace_plan was the only place
guest_homelived, and reading it throughplan.workspace_plan.guest_homewas the wrong layer. Hoisted in21a46b97d8:guest_home: stris now a field on the baseBottlePlandataclass, populated by each backend'spreparestep. The contrib providers, this gitconfig path, and the smolmachines_guest_home()helper all readplan.guest_homedirectly.Lift the provider-specific blocks of agent_provision_plan into contrib/claude/agent_provider.py and contrib/codex/agent_provider.py, behind a new AgentProvider ABC and a lazy get_provider() registry (mirrors PRD 0048's contrib convention). agent_provision_plan and runtime_for stay as thin shims so existing callers in backend/{docker,smolmachines}/prepare.py and cli/start.py keep working without per-call edits — the shipping diff in this commit is purely 'who owns the producer'. Adds bot_bottle/_provision_apply.py — the backend-agnostic skills / prompt / declarative-plan apply loops the per-provider default methods will dispatch through in the next commit.BottleBackend.provision now resolves the provider plugin from the plan and dispatches prompt / skills / declarative-apply / supervise-mcp through it. The four hooks the docker + smolmachines backends used to override (provision_skills, provision_prompt, provision_provider_auth, provision_supervise) are gone — the duplicated 50-line implementations under backend/{docker,smolmachines}/provision/{skills,prompt, provider_auth,supervise}.py are deleted. Each backend gains a small supervise_mcp_url(plan) override so the provider plugin can run `claude mcp add` / `codex mcp add` against the right URL: docker returns http://{SUPERVISE_HOSTNAME}:{SUPERVISE_PORT}/ on the compose network alias; smolmachines returns plan.agent_supervise_url which launch.py already pins to a host-loopback port. Removes tests/unit/test_provision_supervise.py — the URL it asserted on now lives on the backend, with no equivalent standalone surface to test against (it's covered by the broader plan / launch integration tests).21a46b97d8to266013095e