PRD 0050: Move provider-specific agent logic into contrib #180

Merged
didericis merged 7 commits from prd-0050-agent-provider-contrib into main 2026-06-03 21:38:14 -04:00
14 changed files with 17 additions and 14 deletions
Showing only changes of commit 266013095e - Show all commits
+1
View File
@@ -76,6 +76,7 @@ class BottlePlan(ABC):
spec: BottleSpec
stage_dir: Path
guest_home: str
git_gate_plan: GitGatePlan
egress_plan: EgressPlan
supervise_plan: SupervisePlan | None
+1
View File
@@ -233,6 +233,7 @@ def resolve_plan(
return DockerBottlePlan(
spec=spec,
stage_dir=stage_dir,
guest_home=guest_home,
slug=slug,
container_name=container_name,
container_name_pinned=container_name_pinned,
+1 -1
View File
@@ -57,7 +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:
return
container_gitconfig = "/home/node/.gitconfig"
didericis marked this conversation as resolved Outdated
Outdated
Review

You were right — workspace_plan was the only place guest_home lived, and reading it through plan.workspace_plan.guest_home was the wrong layer. Hoisted in 21a46b97d8: guest_home: str is now a field on the base BottlePlan dataclass, populated by each backend's prepare step. The contrib providers, this gitconfig path, and the smolmachines _guest_home() helper all read plan.guest_home directly.

You were right — workspace_plan was the only place `guest_home` lived, and reading it through `plan.workspace_plan.guest_home` was the wrong layer. Hoisted in 21a46b97d8f35734fa23165ee2b94d1268ddb8d3: `guest_home: str` is now a field on the base `BottlePlan` dataclass, populated by each backend's `prepare` step. The contrib providers, this gitconfig path, and the smolmachines `_guest_home()` helper all read `plan.guest_home` directly.
container_gitconfig = f"{plan.guest_home}/.gitconfig"
didericis marked this conversation as resolved Outdated
Outdated
Review

/home/node should 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.

`/home/node` should 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.
content = git_gate_render_gitconfig(manifest_bottle.git, GIT_GATE_HOSTNAME)
config_file = plan.stage_dir / "agent_gitconfig"
@@ -172,6 +172,7 @@ def resolve_plan(
return SmolmachinesBottlePlan(
spec=spec,
stage_dir=stage_dir,
guest_home=guest_home,
slug=slug,
bundle_subnet=subnet,
bundle_gateway=gateway,
@@ -36,14 +36,6 @@ from ... import Bottle
from ..bottle_plan import SmolmachinesBottlePlan
# `node` is the agent user from the repo Dockerfile.
_GUEST_HOME = "/home/node"
def _guest_home() -> str:
return _GUEST_HOME
def provision_git(plan: SmolmachinesBottlePlan, bottle: Bottle) -> None:
"""Set up git inside the guest. Runs all three subcases; each
no-ops when its condition isn't met."""
@@ -92,7 +84,7 @@ def _provision_git_gate_config(
manifest_bottle.git, plan.agent_git_gate_host, scheme="http",
)
guest_gitconfig = f"{_guest_home()}/.gitconfig"
guest_gitconfig = f"{plan.guest_home}/.gitconfig"
# Stage the file under the plan's stage_dir so cp_in
# has a stable host path. The plan's stage_dir is cleaned up
# by start.py's session-end teardown.
+2 -2
View File
@@ -123,7 +123,7 @@ class ClaudeAgentProvider(AgentProvider):
agent = plan.spec.manifest.agents[plan.spec.agent_name]
if not agent.skills:
return
skills_dir = _skills_dir(plan.workspace_plan.guest_home)
skills_dir = _skills_dir(plan.guest_home)
bottle.exec(f"mkdir -p {skills_dir}", user="root")
for name in agent.skills:
src = host_skill_dir(name)
@@ -143,7 +143,7 @@ class ClaudeAgentProvider(AgentProvider):
Returns the in-guest path iff the agent has a non-empty
prompt (drives `--append-system-prompt-file`); the file is
copied either way so the path always exists."""
prompt_path = _prompt_path(plan.workspace_plan.guest_home)
prompt_path = _prompt_path(plan.guest_home)
bottle.cp_in(str(plan.prompt_file), prompt_path)
bottle.exec(
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
+2 -2
View File
@@ -168,7 +168,7 @@ class CodexAgentProvider(AgentProvider):
agent = plan.spec.manifest.agents[plan.spec.agent_name]
if not agent.skills:
return
skills_dir = _skills_dir(plan.workspace_plan.guest_home)
skills_dir = _skills_dir(plan.guest_home)
bottle.exec(f"mkdir -p {skills_dir}", user="root")
for name in agent.skills:
src = host_skill_dir(name)
@@ -188,7 +188,7 @@ class CodexAgentProvider(AgentProvider):
Codex reads it via the agent's `Read and follow the
instructions in <path>.` bootstrap (see `prompt_args`); the
file is copied either way so the path always exists."""
prompt_path = _prompt_path(plan.workspace_plan.guest_home)
prompt_path = _prompt_path(plan.guest_home)
bottle.cp_in(str(plan.prompt_file), prompt_path)
bottle.exec(
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
+1
View File
@@ -164,6 +164,7 @@ def _plan(
spec = _spec(supervise=supervise, with_git=with_git, with_egress=with_egress)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=STAGE,
slug=SLUG,
@@ -78,6 +78,7 @@ def _plan(
current_config_dir=Path("/tmp/current-config"),
)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=Path("/tmp/stage"),
slug="demo-abc12",
@@ -78,6 +78,7 @@ def _plan(
current_config_dir=Path("/tmp/current-config"),
)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=Path("/tmp/stage"),
slug="demo-abc12",
@@ -44,6 +44,7 @@ def _plan(tmp: str) -> DockerBottlePlan:
identity="test-teardown-00001",
)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=stage,
git_gate_plan=GitGatePlan(
@@ -40,6 +40,7 @@ def _plan(*, git_user: dict | None = None,
copy_cwd=copy_cwd, user_cwd=user_cwd,
)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=stage_dir or Path("/tmp/stage"),
slug="demo-abc12",
+2
View File
@@ -103,6 +103,7 @@ def _proxy_plan(tmp: str) -> PipelockProxyPlan:
def _docker_plan(spec: BottleSpec, tmp: str) -> DockerBottlePlan:
stage = Path(tmp)
return DockerBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=stage,
git_gate_plan=_git_gate_plan(tmp),
@@ -128,6 +129,7 @@ def _docker_plan(spec: BottleSpec, tmp: str) -> DockerBottlePlan:
def _smolmachines_plan(spec: BottleSpec, tmp: str) -> SmolmachinesBottlePlan:
stage = Path(tmp)
return SmolmachinesBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=stage,
git_gate_plan=_git_gate_plan(tmp),
@@ -120,6 +120,7 @@ def _plan(
current_config_dir=Path("/tmp/current-config"),
)
return SmolmachinesBottlePlan(
guest_home="/home/node",
spec=spec,
stage_dir=stage_dir or Path("/tmp/stage"),
slug="demo-abc12",