One Generation at a Time: Profiles, Restarts, and Last-Known-Good State

For DSH Desktop, the difficult part of changing a profile is not displaying a different name in a menu. The real question is how to replace a live DSH composition without leaving the old Host, renderer, services, or subprocesses partly attached to the new one. This chapter follows that transition and explains what “last-known-good” protects, what it deliberately leaves behind, and why the answer is a restart rather than a live mutation.

A running arrangement of DSH Desktop is treated as a generation: one coherent set of Host services, Web contributions, native resources, and selected profile facts. The previous discussion followed that arrangement from launch to Web surface. Here the focus shifts to time. A profile can be selected, prepared, started, rejected, repaired, or replaced, and each transition changes which state is authoritative.

A profile is a composition with an identity

A DSH profile is best understood as a recipe for a Host, not as a folder label. It brings together bundles, dependencies, patches, and persistence choices. Two profiles may use the same underlying DSH home while composing different capabilities; a custom patch may also deliberately redirect a persistence root. The name tells Desktop which recipe was chosen, but the absolute profile directory and its declarative contents determine what the Loader can actually assemble.

That is why Desktop resolves the active profile before the Host composition begins. The launcher does not infer the answer later from a URL, a command-line argument, a setting, or an environment variable. It establishes one profile identity and exposes it through the generation-scoped desktopProfiles.current service. For the lifetime of that generation, the service reports the same selected name and directory. A plugin reading that value is therefore reading the identity of the composition that is currently alive, not making a guess about which profile a nearby process might happen to use.

The distinction matters when several profiles are visible. Listing profiles is a read-only discovery operation. It should not repair their dependencies, reorder their bundles, or rewrite their patches merely because a tray menu needs to show them. Selection is different: it is a state transition that validates a target, persists the accepted choice, and asks the application to restart. The old generation does not quietly change its profile beneath active services. It finishes its lifecycle, and the next generation starts with the new identity.

The service also treats selection as a concurrency problem. If two callers request the same target while a selection is in progress, they can share the same operation. If one target has already been committed for restart, a competing target cannot overwrite it before the restart happens. A persistence failure releases the pending selection so that another attempt can be made; a restart failure retains the committed target so that retrying does not silently select something else. These rules are not visible in a profile menu, but they prevent a pair of fast clicks or two cooperating plugins from producing an ambiguous next launch.

The public documentation for plugin authors centers the contract on three ideas: an immutable current profile, read-only listing, and selection that takes effect through restart. The current implementation also contains a preparation step and profile creation/deletion operations. That difference in surface detail should be read as a version or API-layer distinction, not silently flattened into one universal interface. The stable architectural claim is narrower and more useful: the identity of the live profile is generation-scoped, and making another profile current requires an explicit transition.

The transition is therefore more than a UI event. It has an initial condition—the current generation and its active profile—an authority boundary—the persisted target—and a termination condition—the disposal of the old generation. The target is not live merely because it was written to disk. It becomes the basis for the next running composition only after that composition has been prepared and started.

Consider what would happen without that rule. A plugin could read the active profile, begin a package operation, and keep a reference to the profile service while another part of the application selected a different profile. If the live Host then changed underneath the first operation, the same reference could describe one profile while its side effects reached another. A native window could be displaying one Web composition while its tray commands addressed another, and a subprocess started for the old profile could outlive the services that created it. The resulting bug would not be a simple “wrong profile” error; it would be an ownership error with several plausible symptoms.

Desktop’s transition sequence makes that ambiguity harder to create. Discovery can be repeated because it is read-only. A selection is validated and persisted before the restart request, so the next launch has a durable target. Duplicate requests for that target can join the same pending operation, while a competing target is refused until the transition completes. Then the old generation is disposed. Services and native resources are not expected to migrate across the boundary; the next Host and renderer receive fresh instances tied to the new profile. This is also why a plugin should reacquire the public service after restart rather than cache it as a process-wide singleton.

The pattern resembles replacing a running configuration under a controlled handoff, not editing a preference in place. It is especially valuable when the profile controls more than appearance: bundles influence which code is loaded, dependencies influence what can resolve, and patches can change how persistence is located. A live mutation would have to coordinate all three while preserving the behavior of active requests. DSH Desktop instead makes the transition observable: a choice is pending, the old arrangement ends, a new arrangement boots, and health determines whether the new arrangement has earned durable status.

stateDiagram-v2 [*] --> Prepared Prepared --> Running: Start selected generation Running --> Running: List profiles without mutation Running --> SelectionPending: Persist validated target SelectionPending --> Disposed: Dispose current generation Disposed --> Booting: Relaunch with target profile Booting --> Healthy: Host, window, and client health succeed Healthy --> Checkpointed: Capture healthy configuration Booting --> Recovery: Startup or composition fails Recovery --> Prepared: Repair, restore, or choose another target Checkpointed --> SelectionPending: Select another profile or mode

The diagram’s most important edge is the one that does not exist: there is no transition from a running profile directly to a second live profile while the first generation’s services remain in place. Desktop changes the composition by ending one coherent ownership period and starting another. That is conservative, but it makes stale references detectable. A plugin must not retain desktopProfiles or desktopPnpm globally and continue using it after the generation has been disposed; the next generation supplies a new service instance and a new profile identity.

This model also clarifies why mode changes follow the same boundary. A presentation mode affects which layout or root registration owns the Web surface, while native material choices affect window construction. They are not merely values that every live component can reinterpret safely. Restarting lets the Host, Client, and native shell agree on one set of ownership facts from the beginning. The cost is visible: changing profiles or modes takes time and can interrupt active work. The benefit is that a single process does not have to make old and new roots, services, and native resources coexist while they disagree about who is in charge.

Healthy state is narrower than copied state

“Last-known-good” sounds like a complete backup until its scope is made explicit. In DSH Desktop, a healthy startup is the point at which the active configuration has passed through the relevant gates: the Host has started, the native window has mounted, and the client has reported health. Only then does Desktop capture a configuration checkpoint for that profile. The checkpoint records the active profile’s declarative package and patch files together with shared Harness-home settings and patches. It is evidence that a particular composition reached a healthy starting state, not a snapshot of everything the user owns.

Credentials are excluded. So are sessions, storage, caches, and generated dependency state. That separation is essential. Copying a profile’s configuration can help restore the recipe that tells DSH what to load, but copying conversation records or credentials would create a different privacy and consistency problem. It could also make a recovery image appear complete while omitting the state that a tool or model actually depends on. Desktop’s checkpoint design therefore favors restoring declarative control over silently duplicating content.

This distinction gives different failures different meanings. If a patch points to the wrong bundle or a configuration change prevents the Host from composing, restoring declarative files can address the cause. If a session record is damaged, a credential has expired, or a third-party service is unavailable, a configuration restore may do nothing. If generated dependencies are absent or stale, the profile may need to be materialized again before the Loader can use the restored recipe. Calling all of these cases “rollback” would promise more than the checkpoint contains.

A checkpoint is also a statement about the point at which Desktop had evidence of a healthy start, not a guarantee that the profile will remain healthy forever. A later package operation can change the composition after the checkpoint was captured. A plugin can behave differently when a user invokes a new tool. A model provider can change its response or become unreachable. The checkpoint gives Recovery a known declarative reference, but it does not freeze the entire environment or replace the need to inspect the next startup.

The shared DSH home makes the boundary less obvious to users. Official profiles normally use the same home for sessions, settings, and storage, so switching profiles does not require a migration step for those records. But that convenience does not mean the profiles are identical. Their bundles and dependencies remain separate, and Desktop does not silently copy plugins from the old profile into the new one. A command issued after the switch targets the newly active profile; preparing a different profile requires an explicit profile selection. If a custom patch redirects a persistence root, that profile’s configuration wins, which is another reason not to assume that every profile shares the same data location.

There is also an important discrepancy in the repository’s reader-facing descriptions. The user guide describes a failed profile startup as returning to the previous working choice, while the package README says startup does not substitute a last-known-good profile or automatically mutate configuration after a failure. The higher-confidence conclusion that both descriptions support is limited: only a healthy startup is committed as last-known-good, and an unhealthy attempt must enter a recovery path rather than being treated as a successful replacement. The exact automatic fallback behavior is a version or documentation difference and should not be presented as a universal guarantee without resolving that difference.

Package changes expose the same boundary in another form. DSH Desktop provides desktopPnpm so a plugin can use the packaged pnpm environment or the packaged DSH plugin semantics. The service owns the executable path, process environment, output streams, cancellation, and the rule that only one package operation runs within a generation. It does not automatically snapshot an install, roll it back, retry it, or decide whether a package is trustworthy. The caller must reconcile the profile’s bundle state and validate the result, after which a restart brings the changed composition into the Loader path.

This division prevents a misleading promise. A package operation can be process-safe enough to cancel and wait for its child tree, yet still leave a domain-level problem: a dependency may be incompatible, a bundle may be declared incorrectly, or a plugin may load and behave badly. Healthy-start checkpoints offer a separate recovery mechanism for configuration, but they do not turn every package mutation into an atomic transaction. The user or plugin author must decide when an operation is complete and what evidence is sufficient to accept it.

Restart fencing is consequently a trade-off, not a mark of perfection. It reduces mixed-generation bugs by making ownership periods explicit. It makes stale service references fail instead of silently acting on a different profile. It gives native materials, Loader rows, and layout ownership a clean construction point. But it also means a plugin update is not instantly visible, a profile switch can interrupt a task, and state outside the checkpoint remains outside the rollback boundary. A system that promises live reconfiguration would need stronger coordination for every active service, renderer surface, subprocess, and persistence root; DSH Desktop chooses not to make that promise.

The result is a useful distinction between selection, startup, and health. Selection says what should be tried next. Startup creates the next composition. Health says whether that composition earned a durable checkpoint. Recovery acts on the gap between those statements. Once the reader sees those as separate states, profile management stops looking like a menu feature and becomes what it is: controlled replacement of a running system.

That replacement raises the next boundary question. If every generation has its own profile and services, how should plugins use Desktop capabilities without reaching into the private machinery that creates them? The answer depends on treating the desktop shell as a plugin too—but only some of its capabilities are public, and the distinction determines whether composition remains maintainable.

References