Allow stopping ws0 independently of other workspaces

Remove the guard in stop-devenv that refused to stop ws0 while any
ws1+ instance was running. Each workspace is now fully independent
and can be started/stopped in any order. Shared infra shuts down
only when no instances remain running.

Updated docs (devenv.md, agentic-devenv.md) and devenv memory to
reflect the new behavior.

AI-assisted-by: mimo-v2.5-pro
This commit is contained in:
Andrey Antukh 2026-08-01 11:26:10 +00:00
parent 5ad0ff752e
commit 32520e66e5
5 changed files with 48 additions and 46 deletions

View File

@ -25,7 +25,9 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par
## Worker policy
Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. ws0 must be running whenever any ws1+ is running, and is the last instance to stop — `run-devenv --agentic --ws N` (N≥1) auto-starts ws0 first; `stop-devenv` refuses to stop ws0 while any ws1+ is up. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`.
Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`.
Each workspace is independent and can be started/stopped in any order. Shared infra (postgres, minio, etc.) is shut down only when no instances remain running.
## Port layout
@ -63,8 +65,8 @@ No `--delete` on the working-tree pass: gitignored caches in the workspace survi
## CLI surface
- `run-devenv --agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet). Auto-starts ws0 first when the target is ws1+ and ws0 is not yet up.
- `stop-devenv [--ws main|0|wsN|N] [--all]`: stop instances. Flags mutually exclusive. `--ws N` (N≥1) stops just that workspace. `--ws 0` or no flag stops ws0 + shared infra, refused while any ws1+ is running. `--all` stops every ws highest-first then ws0, then infra.
- `run-devenv --agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet).
- `stop-devenv [--ws main|0|wsN|N] [--all]`: stop instances. Flags mutually exclusive. `--ws N` stops just that workspace. `--ws 0` or no flag stops ws0; shared infra shuts down only if no other instances remain. `--all` stops every ws highest-first then ws0, then infra.
- `run-devenv`: legacy alias, ws0 non-agentic attached.
- `attach-devenv [--ws main|0|wsN|N]`: pure attach. Fails fast if instance/session missing.
- `run-devenv-shell [--instance 0|wsN|N] [cmd...]`: bash in target instance. (`--instance` flag not yet renamed to `--ws`.)

View File

@ -100,7 +100,7 @@ RUN set -eux; \
FROM base AS setup-opencode
ENV OPENCODE_VERSION=1.18.4
ENV OPENCODE_VERSION=1.18.11
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
@ -160,7 +160,7 @@ RUN set -eux; \
FROM base AS setup-jvm
# https://clojure.org/releases/tools
ENV CLOJURE_VERSION=1.12.5.1654
ENV CLOJURE_VERSION=1.12.5.1664
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
@ -242,12 +242,12 @@ RUN set -eux; \
FROM base AS setup-utils
ENV CLJKONDO_VERSION=2026.05.25 \
BABASHKA_VERSION=1.12.218 \
CLJFMT_VERSION=0.16.4 \
PIXI_VERSION=0.67.2 \
GITHUB_CLI_VERSION=2.96.0 \
UV_VERSION=0.11.9 \
ENV CLJKONDO_VERSION=2026.07.24 \
BABASHKA_VERSION=1.13.219 \
CLJFMT_VERSION=0.16.5 \
PIXI_VERSION=0.75.0 \
GITHUB_CLI_VERSION=2.97.0 \
UV_VERSION=0.12.1 \
UV_TOOL_DIR=/opt/uv/tools \
UV_TOOL_BIN_DIR=/opt/utils/bin \
UV_PYTHON_INSTALL_DIR=/opt/uv/python \

View File

@ -55,7 +55,7 @@ Serena, Playwright) and supports a launcher that wires them into your AI client.
for Penpot development (see below for details).
6. **Shut down workspaces** with `./manage.sh stop-devenv`, either one by one or all at once.
You cannot shut down `ws0` if any other workspace is still running, since it's the worker-bearer.
Each workspace is independent and can be stopped in any order.
Shared infrastructure will be cleaned up when the last workspace is stopped.
Optional: watch Serena's activity in its dashboard
@ -153,8 +153,8 @@ automatically, so regular users never run this.
Brings one agentic instance up. Errors out if the target is already running.
`--ws N` (N ≥ 1) auto-starts ws0 first if it is not already up - ws0 is the
worker-bearer and must be running whenever any wsN is. Per-instance ports
`--ws N` (N ≥ 1) brings that workspace up independently — workspaces can be
started and stopped in any order. Per-instance ports
are offset by `10000 × N` (ws1's MCP at `http://localhost:14401/mcp`, Serena
MCP at `http://localhost:24181`, Serena dashboard at
`http://localhost:24182`, etc.). `manage.sh` prints the full URL set on

View File

@ -69,13 +69,12 @@ The devenv runs as separate compose projects:
(default `~/.penpot/penpot_workspaces/`). You can explicitly sync them
with the `--sync` flag (automatic on first start).
Each call to `run-devenv` brings up one instance, and ws0 is always
running whenever any ws1+ is — `--ws N` (N≥1) auto-starts ws0 first if it
isn't already up:
Each call to `run-devenv` brings up one instance. Workspaces are independent
and can be started and stopped in any order:
```bash
./manage.sh run-devenv # main (ws0)
./manage.sh run-devenv --ws 1 # ws0 if needed, then ws1
./manage.sh run-devenv --ws 1 # ws1
./manage.sh run-devenv --ws 2 --sync # ws2, re-seeding from the live repo
```
@ -91,12 +90,12 @@ the frontend's MCP flag) is copied into each workspace on its initial sync
only. After that the developer maintains it in each workspace; subsequent
`--sync` runs leave the workspace copy alone.
Stopping mirrors the start invariant — ws0 is the last to stop, and shared
infra stops with it:
Stopping is equally flexible — each workspace is independent. Shared infra
stops only when no instances remain running:
```bash
./manage.sh stop-devenv --ws 1 # stops ws1; ws0 + infra stay up
./manage.sh stop-devenv # stops ws0 + infra; errors if ws1+ still running
./manage.sh stop-devenv # stops ws0; infra stays up if ws1+ still running
./manage.sh stop-devenv --all # stops every ws1+ first, then ws0 + infra
```
@ -150,12 +149,14 @@ file-summary cache, rate-limit counters) isolated.
Background workers (`enable-backend-worker`) run only on ws0 — ws1+ overlays
disable it. ws1+ RPC handlers still enqueue tasks into the shared Postgres
`task` table; ws0's dispatcher claims them via `FOR UPDATE SKIP LOCKED` and
runs them against the shared DB and MinIO. The "ws0 always up when ws1+ is
up" invariant exists for this reason: it keeps a single worker-bearer and
avoids the multi-instance cron-dedup race (the lock on `scheduled_task` is
released when the task body finishes, so two cron timers firing the same
scheduled instant with a gap larger than the body's runtime can both
execute it).
runs them against the shared DB and MinIO. Workers are fire-and-forget:
`wrk/submit!` inserts a row and returns; RPC handlers never wait on
completion. The "ws0 only" policy avoids multi-instance worker races (cron
dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across
submitters).
Each workspace is independent and can be started and stopped in any order.
Shared infrastructure shuts down only when no instances remain running.
### Upgrading from a pre-parallel devenv

View File

@ -511,11 +511,12 @@ function create-devenv {
instance-compose ws0 create
}
# Stop instances. ws0 is the worker-bearer and must be the last one to stop;
# shared infra is shut down together with ws0. Flags are mutually exclusive.
# Stop instances. Each workspace is independent and can be stopped in any
# order. Shared infra is shut down only when no instances remain running.
# Flags are mutually exclusive.
#
# --ws N (N>=1) stop just that workspace. Leaves ws0 and infra alone.
# --ws 0 | (no flag) stop ws0 + shared infra. Refused if any ws1+ is running.
# --ws 0 | (no flag) stop ws0; shuts down shared infra only if no ws1+ is running.
# --all stop every wsN highest-first, then ws0, then infra.
function stop-devenv {
local target=""
@ -555,21 +556,19 @@ function stop-devenv {
[[ -z "$target" ]] && target="ws0"
if [[ "$target" == "ws0" ]]; then
local non_main=""
for ws in $running; do
[[ "$ws" != "ws0" ]] && non_main="$non_main $ws"
done
if [[ -n "$non_main" ]]; then
echo "stop-devenv: cannot stop ws0 while other instances are running:${non_main}." >&2
echo "Stop them first (--ws N) or use --all." >&2
return 1
fi
if printf '%s\n' $running | grep -qx ws0; then
stop-instance ws0
else
echo "[ws0] not running."
fi
infra-compose down -t 2
# Only stop shared infra when no other instances are still running.
local remaining=""
for ws in $(list-running-instances); do
[[ "$ws" != "ws0" ]] && remaining="$remaining $ws"
done
if [[ -z "$remaining" ]]; then
infra-compose down -t 2
fi
return 0
fi
@ -1346,14 +1345,14 @@ function usage {
echo " honouring per-repo local overrides; see"
echo " devenv.md > 'Git identity inside the container')."
echo "- create-devenv Create ws0 + shared-infra compose services without starting them."
echo "- stop-devenv Stop one or more workspaces. Shared infra stops with the last one."
echo " Options: --ws N (stop wsN, N >= 1) | (no flag) (stop ws0 + infra;"
echo " refused if any wsN is still running) | --all (stop every wsN"
echo " highest first, then ws0 + infra)."
echo "- stop-devenv Stop one or more workspaces. Shared infra stops when no instances remain."
echo " Options: --ws N (stop wsN) | (no flag) (stop ws0; shared infra only"
echo " if no other ws is running) | --all (stop every wsN highest"
echo " first, then ws0 + infra)."
echo "- drop-devenv Same CLI and invariants as stop-devenv (see above), plus removal of"
echo " the shared devenv image on full teardowns (no flag, --ws 0, or --all)."
echo " Refused if any wsN (N >= 1) is still running. A single --ws N (N >= 1)"
echo " keeps the image since other workspaces still need it."
echo " A single --ws N (N >= 1) keeps the image since other workspaces still"
echo " need it."
echo "- log-devenv Tail a workspace's compose logs."
echo " Options: --ws N (default: 0)."
echo ""