Design a workspace-porting abstraction for bottle start #116

Closed
opened 2026-06-01 23:11:37 -04:00 by didericis-codex · 1 comment
Collaborator

Problem

bot-bottle has several backend-specific assumptions about where the operator's current workspace lands inside a bottle. Docker --cwd layering copies the host cwd into /home/node/workspace and sets that as the image workdir; smolmachines currently copies .git into <guest_home>/workspace/.git; provider setup has started to need path-specific trust/auth state as well.

Those details are currently scattered across image build, git provisioning, provider provisioning, and agent launch behavior. That makes it too easy for a provider-specific fix to encode another one-off /workspace assumption instead of using a coherent "current workspace in the bottle" contract.

Desired outcome

Design a backend-neutral abstraction for porting the current workspace into a bottle at start time, including:

  • the in-bottle workspace path and working directory contract
  • how --cwd content and .git state are copied or mounted
  • ownership and permissions after copy/mount
  • how provider CLIs learn/trust the workspace path
  • how Docker and smolmachines should expose the same logical behavior despite different transport primitives

Notes

This is design work first. Avoid additional provider-specific workspace-path changes in PRD 0029 until this abstraction is settled.

## Problem bot-bottle has several backend-specific assumptions about where the operator's current workspace lands inside a bottle. Docker `--cwd` layering copies the host cwd into `/home/node/workspace` and sets that as the image workdir; smolmachines currently copies `.git` into `<guest_home>/workspace/.git`; provider setup has started to need path-specific trust/auth state as well. Those details are currently scattered across image build, git provisioning, provider provisioning, and agent launch behavior. That makes it too easy for a provider-specific fix to encode another one-off `/workspace` assumption instead of using a coherent "current workspace in the bottle" contract. ## Desired outcome Design a backend-neutral abstraction for porting the current workspace into a bottle at start time, including: - the in-bottle workspace path and working directory contract - how `--cwd` content and `.git` state are copied or mounted - ownership and permissions after copy/mount - how provider CLIs learn/trust the workspace path - how Docker and smolmachines should expose the same logical behavior despite different transport primitives ## Notes This is design work first. Avoid additional provider-specific workspace-path changes in PRD 0029 until this abstraction is settled.
didericis added the Kind/Enhancement label 2026-06-01 23:36:15 -04:00
didericis self-assigned this 2026-06-02 02:56:52 -04:00
Author
Collaborator

Design sketch after reading the current Docker/smolmachines paths:

Proposed abstraction: WorkspacePlan

Introduce a backend-neutral workspace plan resolved during prepare, then let each backend implement the transport details during launch/provisioning.

@dataclass(frozen=True)
class WorkspacePlan:
    enabled: bool
    host_path: Path
    guest_home: str
    guest_path: str              # default: f"{guest_home}/workspace"
    workdir: str                 # usually same as guest_path when enabled, guest_home otherwise
    owner: str = "node:node"
    mode: str = "755"
    copy_contents: bool = True
    copy_git: bool = True
    has_host_git_dir: bool = False

BottleSpec should keep the user intent (copy_cwd, user_cwd). BottlePlan should carry the resolved workspace_plan so all later code uses one contract instead of re-reading spec.copy_cwd and spelling /home/node/workspace again.

Relevant current hard-coded sites:

Contract

When --cwd is enabled:

  • The logical workspace path inside every bottle is workspace_plan.guest_path, defaulting to /home/node/workspace.
  • The agent process starts with workspace_plan.workdir as its working directory.
  • The copied workspace contents are owned by node:node and writable by node.
  • If the host cwd has a .git directory, the guest workspace has the equivalent .git at <guest_path>/.git before the agent is attached.
  • Provider trust/config should be written for workspace_plan.guest_path, not for an independently chosen path.

When --cwd is disabled:

  • workspace_plan.enabled == False.
  • workspace_plan.workdir falls back to the provider guest home, currently /home/node.
  • No workspace content or .git copy occurs.
  • Provider trust can remain home-scoped, matching current behavior.

This keeps /home/node/workspace as the default path, but makes it a resolved value rather than a convention each subsystem rediscoveres.

Implementation shape

  1. Add a small module, probably bot_bottle/workspace.py, with:

    • WorkspacePlan
    • workspace_plan(spec, guest_home) -> WorkspacePlan
    • maybe constants for default user/owner/path suffix
  2. Add workspace_plan: WorkspacePlan to BottlePlan or to both concrete plans. I prefer BottlePlan because the contract is backend-neutral and provider logic should be able to depend on it.

  3. Resolve it in each backend prepare using that backend's guest home override:

    • Docker: BOT_BOTTLE_CONTAINER_HOME, default /home/node
    • smolmachines: BOT_BOTTLE_GUEST_HOME, default /home/node
  4. Change provider provisioning to accept the workspace path:

    • Extend agent_provision_plan(..., trusted_project_path: str = guest_home) or pass the full WorkspacePlan if that does not create an awkward dependency.
    • For Codex, generate [projects."<trusted_project_path>"] instead of always [projects."<guest_home>"].
    • For Claude, move the trust-entry patch out of build_image_with_cwd and into provider provisioning, or make the Docker image build receive workspace_plan.guest_path. Moving it into provider provisioning is cleaner because smolmachines then gets the same trust behavior.
  5. Replace backend-specific .git copy helpers with workspace-aware helpers:

    • Docker uses docker cp to copy .git into {workspace_plan.guest_path}/.git, then chown -R node:node on the copied .git or the whole workspace if needed.
    • smolmachines uses machine cp and machine_exec, but targets the same workspace_plan.guest_path.
    • The condition becomes workspace_plan.enabled and workspace_plan.has_host_git_dir.
  6. Backend transport:

    • Docker can keep the derived-image approach for now: build from workspace_plan.host_path, COPY --chown=node:node . {workspace_plan.guest_path}, WORKDIR {workspace_plan.workdir}.
    • smolmachines should explicitly copy workspace contents during provisioning or VM init if full content copy is required. Today it only copies .git; that means the desired logical parity is not actually met unless the packed image already contains the cwd content through some other path.
    • If smolmachines has a better primitive later (mount, rsync, image layer), it still satisfies the same plan.

Suggested tests

  • Unit test workspace_plan() for enabled/disabled, guest-home override, relative/absolute host cwd, and .git detection.
  • Docker unit test that the derived Dockerfile uses workspace_plan.guest_path and workspace_plan.workdir instead of hard-coded /home/node/workspace.
  • Provider unit test that Codex trust is generated for the workspace path when --cwd is enabled and home when disabled.
  • Git provisioning tests for both backends should assert copy targets come from workspace_plan.guest_path.
  • A smolmachines parity test should fail until workspace contents, not only .git, are copied.

Non-goals for the first PRD

  • Do not redesign git-gate or manifest git entries.
  • Do not add user-facing path flags yet. Keep /home/node/workspace as the default resolved path.
  • Do not switch Docker from image-copy to bind-mount unless there is a separate reason; the abstraction should allow either transport without forcing it.
  • Do not add provider-specific one-off path patches while this is landing.

The main decision to make before implementation is whether smolmachines should copy full workspace contents now. If the desired outcome is true Docker/smolmachines parity for --cwd, I think yes: copying only .git gives the agent repository metadata without the working tree, which is not the same logical workspace behavior as Docker.

Design sketch after reading the current Docker/smolmachines paths: ## Proposed abstraction: `WorkspacePlan` Introduce a backend-neutral workspace plan resolved during `prepare`, then let each backend implement the transport details during launch/provisioning. ```python @dataclass(frozen=True) class WorkspacePlan: enabled: bool host_path: Path guest_home: str guest_path: str # default: f"{guest_home}/workspace" workdir: str # usually same as guest_path when enabled, guest_home otherwise owner: str = "node:node" mode: str = "755" copy_contents: bool = True copy_git: bool = True has_host_git_dir: bool = False ``` `BottleSpec` should keep the user intent (`copy_cwd`, `user_cwd`). `BottlePlan` should carry the resolved `workspace_plan` so all later code uses one contract instead of re-reading `spec.copy_cwd` and spelling `/home/node/workspace` again. Relevant current hard-coded sites: - Docker copies cwd into `/home/node/workspace`, patches Claude trust, and sets `WORKDIR` in [`bot_bottle/backend/docker/util.py`](https://gitea.dideric.is/didericis/bot-bottle/src/commit/44273be9ebee703b42a06db5272b91a7031c2274/bot_bottle/backend/docker/util.py). - Docker git provisioning copies `.git` to `/home/node/workspace/.git` in [`bot_bottle/backend/docker/provision/git.py`](https://gitea.dideric.is/didericis/bot-bottle/src/commit/44273be9ebee703b42a06db5272b91a7031c2274/bot_bottle/backend/docker/provision/git.py). - smolmachines git provisioning separately reconstructs `<guest_home>/workspace/.git` in [`bot_bottle/backend/smolmachines/provision/git.py`](https://gitea.dideric.is/didericis/bot-bottle/src/commit/44273be9ebee703b42a06db5272b91a7031c2274/bot_bottle/backend/smolmachines/provision/git.py). - Codex currently trusts `guest_home`, not the copied workspace, in [`bot_bottle/agent_provider.py`](https://gitea.dideric.is/didericis/bot-bottle/src/commit/44273be9ebee703b42a06db5272b91a7031c2274/bot_bottle/agent_provider.py). ## Contract When `--cwd` is enabled: - The logical workspace path inside every bottle is `workspace_plan.guest_path`, defaulting to `/home/node/workspace`. - The agent process starts with `workspace_plan.workdir` as its working directory. - The copied workspace contents are owned by `node:node` and writable by `node`. - If the host cwd has a `.git` directory, the guest workspace has the equivalent `.git` at `<guest_path>/.git` before the agent is attached. - Provider trust/config should be written for `workspace_plan.guest_path`, not for an independently chosen path. When `--cwd` is disabled: - `workspace_plan.enabled == False`. - `workspace_plan.workdir` falls back to the provider guest home, currently `/home/node`. - No workspace content or `.git` copy occurs. - Provider trust can remain home-scoped, matching current behavior. This keeps `/home/node/workspace` as the default path, but makes it a resolved value rather than a convention each subsystem rediscoveres. ## Implementation shape 1. Add a small module, probably `bot_bottle/workspace.py`, with: - `WorkspacePlan` - `workspace_plan(spec, guest_home) -> WorkspacePlan` - maybe constants for default user/owner/path suffix 2. Add `workspace_plan: WorkspacePlan` to `BottlePlan` or to both concrete plans. I prefer `BottlePlan` because the contract is backend-neutral and provider logic should be able to depend on it. 3. Resolve it in each backend `prepare` using that backend's guest home override: - Docker: `BOT_BOTTLE_CONTAINER_HOME`, default `/home/node` - smolmachines: `BOT_BOTTLE_GUEST_HOME`, default `/home/node` 4. Change provider provisioning to accept the workspace path: - Extend `agent_provision_plan(..., trusted_project_path: str = guest_home)` or pass the full `WorkspacePlan` if that does not create an awkward dependency. - For Codex, generate `[projects."<trusted_project_path>"]` instead of always `[projects."<guest_home>"]`. - For Claude, move the trust-entry patch out of `build_image_with_cwd` and into provider provisioning, or make the Docker image build receive `workspace_plan.guest_path`. Moving it into provider provisioning is cleaner because smolmachines then gets the same trust behavior. 5. Replace backend-specific `.git` copy helpers with workspace-aware helpers: - Docker uses `docker cp` to copy `.git` into `{workspace_plan.guest_path}/.git`, then `chown -R node:node` on the copied `.git` or the whole workspace if needed. - smolmachines uses `machine cp` and `machine_exec`, but targets the same `workspace_plan.guest_path`. - The condition becomes `workspace_plan.enabled and workspace_plan.has_host_git_dir`. 6. Backend transport: - Docker can keep the derived-image approach for now: build from `workspace_plan.host_path`, `COPY --chown=node:node . {workspace_plan.guest_path}`, `WORKDIR {workspace_plan.workdir}`. - smolmachines should explicitly copy workspace contents during provisioning or VM init if full content copy is required. Today it only copies `.git`; that means the desired logical parity is not actually met unless the packed image already contains the cwd content through some other path. - If smolmachines has a better primitive later (mount, rsync, image layer), it still satisfies the same plan. ## Suggested tests - Unit test `workspace_plan()` for enabled/disabled, guest-home override, relative/absolute host cwd, and `.git` detection. - Docker unit test that the derived Dockerfile uses `workspace_plan.guest_path` and `workspace_plan.workdir` instead of hard-coded `/home/node/workspace`. - Provider unit test that Codex trust is generated for the workspace path when `--cwd` is enabled and home when disabled. - Git provisioning tests for both backends should assert copy targets come from `workspace_plan.guest_path`. - A smolmachines parity test should fail until workspace contents, not only `.git`, are copied. ## Non-goals for the first PRD - Do not redesign git-gate or manifest `git` entries. - Do not add user-facing path flags yet. Keep `/home/node/workspace` as the default resolved path. - Do not switch Docker from image-copy to bind-mount unless there is a separate reason; the abstraction should allow either transport without forcing it. - Do not add provider-specific one-off path patches while this is landing. The main decision to make before implementation is whether smolmachines should copy full workspace contents now. If the desired outcome is true Docker/smolmachines parity for `--cwd`, I think yes: copying only `.git` gives the agent repository metadata without the working tree, which is not the same logical workspace behavior as Docker.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#116