diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index 82e4e704f3..0651f872fe 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -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`.) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index b8f106d850..4aff930e9e 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -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 \ diff --git a/docs/technical-guide/developer/agentic-devenv.md b/docs/technical-guide/developer/agentic-devenv.md index 8d943dcd93..55b03c1492 100644 --- a/docs/technical-guide/developer/agentic-devenv.md +++ b/docs/technical-guide/developer/agentic-devenv.md @@ -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 diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md index 5aaab86e7f..922ed24f1d 100644 --- a/docs/technical-guide/developer/devenv.md +++ b/docs/technical-guide/developer/devenv.md @@ -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 diff --git a/manage.sh b/manage.sh index f84f99f41c..be6cc078b1 100755 --- a/manage.sh +++ b/manage.sh @@ -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 ""