feat(orchestrator): slice 4 — consolidated per-host sidecar #358
Open
didericis-claude
wants to merge 2 commits from
orchestrator-slice4 into orchestrator-slice3
pull from: orchestrator-slice4
merge into: didericis:orchestrator-slice3
didericis:main
didericis:orchestrator-registration
didericis:orchestrator-lifecycle
didericis:orchestrator-supervise-writers
didericis:orchestrator-supervise-multitenant
didericis:orchestrator-gitgate-multitenant
didericis:orchestrator-rename-gateway
didericis:orchestrator-slice8
didericis:orchestrator-slice7
didericis:orchestrator-slice6
didericis:prd-orchestrator
didericis:orchestrator-slice5
didericis:orchestrator-slice3
didericis:orchestrator-slice2
didericis:ci-kvm-runner
didericis:post-build-image-smoke-test
didericis:firecracker-backend
didericis:issue-330-cached-images
didericis:forge-native-integration
didericis:prd-smolmachines-linux
didericis:claude-forward-host-credentials
didericis:fold-orchestrator-subpackage
didericis:prd-egress-control-plane
didericis:manifest-break-import-cycle
didericis:dlp-supervise-quality-fixes
didericis:table-drive-dlp-tests
didericis:fix-integration-test-failures
didericis:fix/macos-container-relative-dockerfile
didericis:prd-0054-install-script
didericis:commit-bottle-state
didericis:pr-211
didericis:move-codex-auth-to-contrib
didericis:feat/pipelock-skip-scan-extensions
didericis:prd-0049-named-labelled-agents
didericis:harden-git-gate-shell-rendering
Labels
Clear labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Low
4
The priority is low
Priority
Medium
3
The priority is medium
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: didericis/bot-bottle#358
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "orchestrator-slice4"
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?
Slice 4 of PRD 0070, stacked on #357. The core consolidation win: one persistent sidecar per host, shared by every bottle, instead of a sidecar bundle per bottle — safe because the attribution invariant (source IP + identity token) lets the sidecar map each request to the right bottle.
sidecar.py— a backend-neutralSidecarlifecycle contract (mirrorsLaunchBroker) + aDockerSidecar. Defining behaviour is idempotent singleton:ensure_runningstarts the instance if absent and is a no-op if it's already up (N launches never spawn N sidecars);stopis idempotent.dockerutil.py— a sharedrun_dockerhelper;DockerBrokernow uses it too (DRY with slice 3).service.py— the Orchestrator holds an optionalSidecar, exposesensure_sidecar()+sidecar_status();GET /sidecarreports it;__main__ --sidecar-imageensures the single sidecar on startup.Tests: unit (docker mocked) — is_running, ensure idempotent (no-op when up / starts when absent), failure raises, stop idempotent; Orchestrator sidecar wiring/status; control-plane
/sidecar; integration (gated) — a real idempotent singleton (one container after two ensures), stop removes. Full suite green (only pre-existing/bin/sleeperrors); integration verified locally against real docker.Merge order: #352 → #356 → #357 → this.
🤖 Generated with Claude Code
The core consolidation win: one persistent sidecar per host, shared by every bottle, instead of a sidecar bundle per bottle. Safe to share because the attribution invariant (source IP + identity token) lets the sidecar map each request to the right bottle. * orchestrator/sidecar.py — a backend-neutral `Sidecar` lifecycle contract (mirrors LaunchBroker) + a `DockerSidecar` impl. The defining behaviour is idempotent singleton: `ensure_running` starts the instance if absent and is a no-op if it's already up, so N launches never spawn N sidecars; `stop` is idempotent. * orchestrator/dockerutil.py — a shared `run_docker` helper; DockerBroker now uses it too (DRY with slice 3). * service.py — the Orchestrator holds an optional `Sidecar`, exposes `ensure_sidecar()` + `sidecar_status()`. * control_plane.py — `GET /sidecar` reports it; __main__ gains `--sidecar-image` and ensures the single sidecar on startup. Tests: unit (docker mocked) — is_running, ensure idempotent (no-op when up, starts when absent), failure raises, stop idempotent; Orchestrator sidecar wiring/status; control-plane /sidecar; integration (gated) — ensure is a real idempotent singleton (one container after two ensures), stop removes. Full suite green (only pre-existing /bin/sleep errors); integration verified locally against real docker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck@@ -0,0 +6,4 @@import subprocessdef run_docker(argv: list[str]) -> subprocess.CompletedProcess[str]:Why not use the docker_mod util? Are there too many deps in it/does it create circular deps? If so that's a bit of a smell, this should really be placed there.
Good instinct, and I dug in.
docker_mod(backend.docker.util) is lean itself (only imports...log), but importing it isn't cheap: it runsbackend/__init__.py, which eagerly imports all three bottle backends (docker + firecracker + macos) plus the manifest/egress/git-gate/supervise/workspace framework. Measured:from bot_bottle.backend.docker import utilpulls in 76bot_bottlemodules, including the entire firecracker and macos backends.Since
orchestrator/__init__importsDockerBroker/DockerSidecar, routing them throughdocker_modwould make every orchestrator import (even the lean registry/control-plane tests) load all three backends — coupling the deliberately-lean orchestrator to the whole backend layer for a 3-linesubprocesswrapper. No circular dep, but real weight.So you're right it's a smell — but the smell is the eager all-backends import in
backend/__init__.py, not the helper. Rather than pick between "parallel util" and "drag in the backend framework", I promoted it to a top-level, framework-freebot_bottle/docker_cmd.py(single stdlib import) — a proper shared home the orchestrator uses now andbackend.docker.utilcan adopt later (6765289). Verifiedimport bot_bottle.orchestratorstays lean afterward: 12 modules, no firecracker/macos backends.Two follow-ups worth a separate cleanup if you want them: (a) make
backend/__init__.pylazy-load the backends sodocker_modis cheap to import anywhere, and (b) then threadbackend.docker.util's ownsubprocess.run(["docker", ...])calls throughdocker_cmd.run_dockerfor full DRY.@didericis-claude file an issue for those follow ups (can put in the same issue)
Filed both in #359 (one issue, two ordered parts): (1) make
backend/__init__.pylazy-load the backends so importing a backend util doesn't drag in all three backends (~76 modules), then (2) DRYbackend.docker.util's docker calls ontodocker_cmd.run_docker. Neither blocks the orchestrator stack.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.