From e18e002d2fef243eeaf1ae57998a6a1731bebf85 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 14 Aug 2026 20:37:42 +0200 Subject: [PATCH] CB-559: a profile's launch settings are deferred, not hot The shipped docs and javadoc said a profile's `model` and `tabLabel` take effect on the next spawn. They do not, and ConfigRef did not detect the change either, so a reload logged a clean "config reloaded" and silently did nothing. That is the worst outcome a reload can produce: the operator has no reason to doubt it. What makes a key hot is who reads it and when, not that it is config. Placement reads weight and maxLoad through a supplier on CompositePeerLauncher, so those are genuinely hot. HerdrPeerLauncher takes Map.copyOf(profiles) at construction and resolves every spawn out of that copy, so model, baseUrl, argv, env and the rest cannot move until the daemon restarts. changedDeferredKeys now compares every launch component of an existing profile, excluding weight and maxLoad, and names the profiles that need a restart. The javadoc and bridged.example.yaml say the same thing. Two tests pin the pair: weight/maxLoad reports nothing deferred, a changed model reports the profile by name. --- bridged/bridged.example.yaml | 14 ++-- .../dev/ltms/bridged/config/ConfigRef.java | 74 ++++++++++++++++--- .../ltms/bridged/config/ConfigRefTest.java | 47 +++++++++++- 3 files changed, 114 insertions(+), 21 deletions(-) diff --git a/bridged/bridged.example.yaml b/bridged/bridged.example.yaml index 9e18d9f..0752f19 100644 --- a/bridged/bridged.example.yaml +++ b/bridged/bridged.example.yaml @@ -232,13 +232,17 @@ placement: weighted # Not every key can move under a running daemon, and the difference is about what already exists # when the reload happens — not about how important the key is: # HOT → takes effect on the next spawn: the whole `fleet:` block (every role pool and -# `tabLabel`), `placement:`, and an existing profile's weight / maxLoad / model / -# tabLabel. +# `tabLabel`), `placement:`, and an existing profile's weight / maxLoad. Those are +# hot because the placement policy reads them through a supplier — being config is +# not by itself enough to make a key hot. # DEFERRED → accepted into the new config, but the wiring built at startup keeps the old value # until you restart: `lifecycle:`, `leadHeartbeat:`, `guard:`, `worktreeRoot:`, -# `spawnReadyTimeoutMs` / `spawnReadyPollMs`, and ADDING or REMOVING a profile (a new -# backend needs its own launcher, and launchers are built once). The reload logs -# these by name rather than pretending they applied. +# `spawnReadyTimeoutMs` / `spawnReadyPollMs`, ADDING or REMOVING a profile (a new +# backend needs its own launcher, and launchers are built once), AND an existing +# profile's launch settings — model, baseUrl, argv, env, configDir, mcpUrl, tabLabel. +# The launcher takes a copy of `profiles:` at startup and resolves every spawn out of +# that copy, so those never reach a launch until you restart. The reload logs them by +# name rather than pretending they applied. # COLD → cannot change at all: `bind:`, `herdrSocket:`, `broker:` and `auth:`. The socket is # bound, the broker connection is open, and the auth mode decides who may reach the # port that is already listening. diff --git a/bridged/src/main/java/dev/ltms/bridged/config/ConfigRef.java b/bridged/src/main/java/dev/ltms/bridged/config/ConfigRef.java index 1750234..37001d5 100644 --- a/bridged/src/main/java/dev/ltms/bridged/config/ConfigRef.java +++ b/bridged/src/main/java/dev/ltms/bridged/config/ConfigRef.java @@ -7,6 +7,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -27,13 +28,18 @@ import java.util.function.Supplier; *