From a36e19c6fae6a01d4af8dc51b48959daead2dba3 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Sun, 27 Sep 2026 07:45:51 +0200 Subject: [PATCH 1/4] fix: keep Lavish server address local to each home --- .../skills/operational-home-layout/SKILL.md | 2 +- bin/fm-config-inherit-lib.sh | 4 +--- docs/configuration.md | 3 ++- tests/fm-secondmate-harness.test.sh | 19 +++++++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.agents/skills/operational-home-layout/SKILL.md b/.agents/skills/operational-home-layout/SKILL.md index 71824422f50..281cb20a968 100644 --- a/.agents/skills/operational-home-layout/SKILL.md +++ b/.agents/skills/operational-home-layout/SKILL.md @@ -35,7 +35,7 @@ config/startup-memory-budget primary-authoritative per-home startup-memory b config/stow-pass-horizon optional presence flag opting this home in to /stow's default-off pass-count decay horizon; LOCAL, gitignored, and not inherited; see docs/configuration.md "Stow pass horizon" config/herdr-presentation-spaces optional "off" opt-out from, or "on" opt-in to, Herdr's default-on disposable single-task visual projection, which is unconfigured-default-on only at or above a Herdr version floor; LOCAL, gitignored; inherited by secondmate homes; see docs/herdr-backend.md "Presentation spaces" config/trace-context optional presence flag enabling default-off native W3C trace-context propagation to spawned agents; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Trace context propagation" and docs/trace-context.md -config/lavish-axi-host optional one-line per-machine Lavish server address; LOCAL, gitignored, inherited by secondmate homes, and exported into every worker launch; see docs/configuration.md "Lavish server address" for opening versus polling +config/lavish-axi-host optional one-line per-machine Lavish server address; LOCAL, gitignored, not inherited by secondmate homes, and exported into every worker launch; see docs/configuration.md "Lavish server address" for opening versus polling config/brief-include.md optional standing worker instructions appended verbatim as the last section of every ship and scout scaffold; LOCAL, gitignored, and not inherited; keep its text out of `## Firstmate spec`; see docs/configuration.md "Home brief include" config/fleet-ledger optional presence flag opting this home in to the default-off fleet activity ledger state/fleet-ledger.jsonl that outside tools can follow; LOCAL, gitignored, and not inherited; see docs/fleet-ledger.md config/turnend-churn-absorb optional presence flag opting this home into the default-off absorb of bare turn-end wakes on pane churn; LOCAL, gitignored, and not inherited; see docs/configuration.md "Turn-end pane-churn absorb" diff --git a/bin/fm-config-inherit-lib.sh b/bin/fm-config-inherit-lib.sh index b037b21588c..40ff8ce6690 100644 --- a/bin/fm-config-inherit-lib.sh +++ b/bin/fm-config-inherit-lib.sh @@ -16,8 +16,6 @@ # "off" preferences propagate as files. Primary # config/trace-context is copied at the launch convergence point as part of the # default-off W3C trace-context setup, while live convergence leaves it unchanged. -# Primary config/lavish-axi-host carries the one per-machine Lavish server address -# to every worker so a worker never starts a second server on another interface. # The primary passes its frozen home-session decision into a newly launched # Secondmate; see docs/trace-context.md. # Primary config/claude-permission-mode is a captain-wide safety preference @@ -79,7 +77,7 @@ FM_SHARED_CAPTAIN_MODE="444" # The declared inheritable set (space-separated, config-dir-relative item paths). # Extend here to inherit more of the primary's local config; override via the # environment only in tests. Items must not contain whitespace. -FM_INHERITABLE_CONFIG="${FM_INHERITABLE_CONFIG:-crew-dispatch.json dispatch-never-send crew-harness backlog-backend backend herdr-presentation-spaces startup-memory-budget trace-context launch-env-allowlist claude-permission-mode lavish-axi-host keep-ai-trailers}" +FM_INHERITABLE_CONFIG="${FM_INHERITABLE_CONFIG:-crew-dispatch.json dispatch-never-send crew-harness backlog-backend backend herdr-presentation-spaces startup-memory-budget trace-context launch-env-allowlist claude-permission-mode keep-ai-trailers}" # Items whose value is a home-SESSION enablement decision rather than durable # local configuration. They are inherited at the launch convergence point, where diff --git a/docs/configuration.md b/docs/configuration.md index 340e839e7d7..331bf82a717 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -879,7 +879,8 @@ A remote secondmate is launched on its host from its own home's configuration, s ## Lavish server address (config/lavish-axi-host) The optional local, gitignored `config/lavish-axi-host` contains one non-empty address without whitespace for the per-machine Lavish server. -`fm-spawn.sh` exports that address into every new worker and relaunch for opening boards, and the file is inherited into secondmate homes through the primary-authoritative configuration contract. +`fm-spawn.sh` exports that home's address into every new worker and relaunch for opening boards. +Each secondmate home keeps its own per-machine value; the primary does not copy or overwrite it. Once a board exists, the process-event adapter derives the polling address from that board's own saved Lavish session instead; its header owns the lookup contract. When the file is absent, worker launches do not add a board address and retain the existing ambient-environment behavior. diff --git a/tests/fm-secondmate-harness.test.sh b/tests/fm-secondmate-harness.test.sh index 88ed39720f1..54933af5f19 100755 --- a/tests/fm-secondmate-harness.test.sh +++ b/tests/fm-secondmate-harness.test.sh @@ -380,20 +380,26 @@ test_propagate_lib() { [ -d "$dest/crew-harness" ] || fail "failed absence mirror removed the wrong path" rm -rf "$dest/crew-harness" - # 5. secondmate-harness is never inherited; backend still is + # 5. secondmate-harness and the per-machine Lavish address are never inherited; backend still is printf 'grok\n' > "$src/secondmate-harness" + printf 'primary.example\n' > "$src/lavish-axi-host" printf '{"default":{"harness":"codex"}}\n' > "$src/crew-dispatch.json" printf 'codex\n' > "$src/crew-harness" printf 'manual\n' > "$src/backlog-backend" printf 'herdr\n' > "$src/backend" rm -rf "$d/home2" mkdir -p "$d/home2/config" "$d/home2/state" + printf 'secondmate.example\n' > "$d/home2/config/lavish-axi-host" propagate_inheritable_config "$src" "$d/home2/config" [ -e "$d/home2/config/secondmate-harness" ] && fail "secondmate-harness was inherited (must not be)" + [ "$(cat "$d/home2/config/lavish-axi-host")" = secondmate.example ] || fail "per-machine Lavish address was overwritten" [ "$(cat "$d/home2/config/crew-dispatch.json")" = '{"default":{"harness":"codex"}}' ] || fail "crew-dispatch.json not propagated alongside" [ "$(cat "$d/home2/config/crew-harness")" = codex ] || fail "crew-harness not propagated alongside" [ "$(cat "$d/home2/config/backlog-backend")" = manual ] || fail "backlog-backend not propagated alongside" [ "$(cat "$d/home2/config/backend")" = herdr ] || fail "backend not propagated alongside" + rm -f "$d/home2/config/lavish-axi-host" + propagate_inheritable_config "$src" "$d/home2/config" + [ ! -e "$d/home2/config/lavish-axi-host" ] || fail "primary Lavish address was copied into a home without its own value" # 6. nothing to propagate -> destination dir is never created (a true no-op) rm -rf "$d/src3" "$d/dest3" @@ -1044,7 +1050,7 @@ new_world() { [ "$dispatch_ignore" = no ] || printf 'config/crew-dispatch.json\n' printf 'config/crew-harness\nconfig/secondmate-harness\nconfig/backlog-backend\n' printf 'config/backend\nconfig/herdr-presentation-spaces\nconfig/startup-memory-budget\n' - printf 'config/claude-permission-mode\n' + printf 'config/claude-permission-mode\nconfig/lavish-axi-host\n' } > "$w/main/.gitignore" printf 'v1\n' > "$w/main/AGENTS.md" printf 'r1\n' > "$w/main/README.md" @@ -1302,6 +1308,8 @@ test_bootstrap_sweep_propagates_and_reconverges() { printf 'tmux\n' > "$w/home/config/backend" : > "$w/home/config/trace-context" printf 'grok\n' > "$w/home/config/secondmate-harness" + mkdir -p "$w/sm/config" + printf 'secondmate.example\n' > "$w/sm/config/lavish-axi-host" run_bootstrap "$w" >/dev/null [ "$(cat "$w/sm/config/crew-harness" 2>/dev/null)" = codex ] \ || fail "sweep: crew-harness not pushed into the live home" @@ -1315,12 +1323,15 @@ test_bootstrap_sweep_propagates_and_reconverges() { || fail "sweep: trace-context changed a legacy live home before relaunch" [ -e "$w/sm/config/secondmate-harness" ] \ && fail "sweep: secondmate-harness was inherited (must not be)" + [ "$(cat "$w/sm/config/lavish-axi-host")" = secondmate.example ] \ + || fail "sweep: secondmate's per-machine Lavish address was removed" # Re-converge: primary changes inherited config values; the home follows on the next sweep. printf '{"default":{"harness":"claude"}}\n' > "$w/home/config/crew-dispatch.json" printf 'claude\n' > "$w/home/config/crew-harness" printf 'tasks-axi\n' > "$w/home/config/backlog-backend" printf 'zellij\n' > "$w/home/config/backend" + printf 'primary.example\n' > "$w/home/config/lavish-axi-host" run_bootstrap "$w" >/dev/null [ "$(cat "$w/sm/config/crew-harness" 2>/dev/null)" = claude ] \ || fail "sweep: home did not re-converge to the primary's new crew-harness" @@ -1330,6 +1341,8 @@ test_bootstrap_sweep_propagates_and_reconverges() { || fail "sweep: home did not re-converge to the primary's new backlog-backend" [ "$(cat "$w/sm/config/backend" 2>/dev/null)" = zellij ] \ || fail "sweep: home did not re-converge to the primary's new backend" + [ "$(cat "$w/sm/config/lavish-axi-host")" = secondmate.example ] \ + || fail "sweep: primary Lavish address overwrote the secondmate's value" # Mirror absence: primary clears inherited config; the home's copies are removed. rm -f "$w/home/config/crew-dispatch.json" "$w/home/config/crew-harness" \ @@ -1343,6 +1356,8 @@ test_bootstrap_sweep_propagates_and_reconverges() { && fail "sweep: home backlog-backend not removed after the primary cleared it" [ -e "$w/sm/config/backend" ] \ && fail "sweep: home backend not removed after the primary cleared it" + [ "$(cat "$w/sm/config/lavish-axi-host")" = secondmate.example ] \ + || fail "sweep: secondmate's per-machine Lavish address changed" pass "B7 bootstrap sweep pushes, re-converges, and mirrors absence; never inherits secondmate-harness" } From d70414b7e5e8ae0175a677d702f517487d3ada62 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Sun, 27 Sep 2026 09:57:16 +0200 Subject: [PATCH 2/4] no-mistakes(document): Fix stale primary-owned Lavish host comment in fm-spawn --- bin/fm-spawn.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index 705ac0f98fd..b46c9749c3b 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -565,8 +565,8 @@ case "$CLAUDE_PERMISSION_MODE" in auto) CLAUDE_PERM_FLAG='--permission-mode auto' ;; *) CLAUDE_PERM_FLAG='--dangerously-skip-permissions' ;; esac -# config/lavish-axi-host is the primary-owned per-machine address for the -# shared Lavish server. Read it once per launch and refuse malformed values so +# config/lavish-axi-host is this home's own per-machine address for the +# shared Lavish server; it is never inherited from the primary. Read it once per launch and refuse malformed values so # every worker reaches the same server instead of starting a second one. if ! LAVISH_AXI_HOST_CONFIG_PRESENT=$(fm_config_source_present "$CONFIG/lavish-axi-host"); then exit 1 From e81f998887b8b81d2c678bfb9e44365571148f00 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Mon, 28 Sep 2026 11:51:28 +0200 Subject: [PATCH 3/4] no-mistakes(document): Reflow Lavish host comment in fm-spawn --- bin/fm-spawn.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index b46c9749c3b..7bf067570e5 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -566,8 +566,9 @@ auto) CLAUDE_PERM_FLAG='--permission-mode auto' ;; *) CLAUDE_PERM_FLAG='--dangerously-skip-permissions' ;; esac # config/lavish-axi-host is this home's own per-machine address for the -# shared Lavish server; it is never inherited from the primary. Read it once per launch and refuse malformed values so -# every worker reaches the same server instead of starting a second one. +# shared Lavish server; it is never inherited from the primary. Read it once +# per launch and refuse malformed values so every worker reaches the same +# server instead of starting a second one. if ! LAVISH_AXI_HOST_CONFIG_PRESENT=$(fm_config_source_present "$CONFIG/lavish-axi-host"); then exit 1 fi From 2832a4ebbcbd36fb8645ea26f1b835d7bb4be7a4 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Mon, 28 Sep 2026 12:01:16 +0200 Subject: [PATCH 4/4] no-mistakes(review): Document manual cleanup for previously inherited Lavish host copies --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index 331bf82a717..a326ddf7344 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -881,6 +881,7 @@ A remote secondmate is launched on its host from its own home's configuration, s The optional local, gitignored `config/lavish-axi-host` contains one non-empty address without whitespace for the per-machine Lavish server. `fm-spawn.sh` exports that home's address into every new worker and relaunch for opening boards. Each secondmate home keeps its own per-machine value; the primary does not copy or overwrite it. +A home that already inherited the primary's address under the earlier inheritance contract keeps that copy until an operator rewrites or removes its `config/lavish-axi-host` once. Once a board exists, the process-event adapter derives the polling address from that board's own saved Lavish session instead; its header owns the lookup contract. When the file is absent, worker launches do not add a board address and retain the existing ambient-environment behavior.