feat(lbm-agent): the agent runs — runtime, system world, and detached hook supervision (D5) - #625
Merged
Merged
Conversation
- runtime::Agent: every input (hook, display poll, timers) through the reconciler, its effects carried out in order. A Start computes the zones when it sends and sends Load+Run in one frame, as C# does. - reconcile: Input::Boot builds the first layout without sending anything; the hook gets it when it asks (Connected, then Stopped), and a hook that survived the previous agent (D5) answers Running and is left alone. - world::SystemWorld: discovery (lbm-display), the profile loaded by the persistence engine (lbm-store), the zones (lbm-layout). - watch: the Linux 2-second poll of C#'s LinuxLayoutFactory (sysfs plug signature, KWin/mutter output files). - lbm-agent CLI: --fake-hook (a private endpoint, nothing hooked) and --config-dir/--data-dir (isolated profiles); without --fake-hook it waits for a hook at the usual endpoint (launching one comes with supervision). End to end on this machine, with the fake hook and a scratch store holding an enabled profile: KScreen discovery, the profile, 2 zones, Load+Run, Loaded, Running. Tests: the loop against the fake hook (hand-over, first change rebuilds then re-hooks, user Stop, late and replaced hooks). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
supervise::HookLauncher, the agent's DaemonProcessManager: on Unreachable it starts the hook beside the agent (or --hook), unless the one it started is still coming up, another hook of this user runs, or it is backing off from a hook that keeps dying. The launch is detached — its own session, its own hook.log — so the hook outlives the agent, and the next agent finds it running and leaves it alone (Boot, then Connected and Running). The hook is told it is driven (LBM_HOOK_UI=1): it used to guess from its parent's path, which named the C# UI. Tested at process level with the agent binary serving a fake hook (--serve-fake-hook, hidden): launched, handed the layout, still running after the agent is killed, then told to quit. Nothing here starts the real hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On Windows the hook's pipe is created as its first instance, which is refused (access denied) while any instance of the old one remains — until the dropped fake's tasks are released and the agent has let go of its end. The test rebound at once and failed on the Windows runner; the replacement now retries, as a hook started right after a crash would. (In production the agent only launches a hook once it has seen the loss and failed to connect, so the old instance is gone by then.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of the v6 plan:
lbm-agentruns. The reconciler (#623) and the hook connection (#624) are tied into an event loop over the real machine, and the agent launches the hook itself when none answers, detached, so the hook outlives it (D5).docs/v6-agent.mdis updated.Runtime
runtime::Agent: every input (hook, display poll, the reconciler's timers) goes through the reconciler, one at a time; its effects are carried out in order. A Start computes the zones when it sends and sends Load+Run in one<Messages>frame, as C# does.Input::Boot, C#'s startupUpdateLayout): the first layout is built and nothing is sent; the hook gets it when it asks (Connected, then Stopped). A hook that survived the previous agent answers Running instead, so it keeps its layout and the mice are not re-grabbed.world::SystemWorld: discovery (lbm-display), the profile loaded by the persistence engine (lbm-store), the zones (lbm-layout).watch: the Linux 2-second poll of C#'sLinuxLayoutFactory(sysfs plug signature, KWin/mutter output files).Supervision (D5)
supervise::HookLauncher, the agent'sDaemonProcessManager: onUnreachableit starts the hook beside the agent (or--hook PATH), unless the one it started is still coming up, another hook of this user runs (C# checked any user's), or it is backing off from a hook that keeps dying (0, 10, 30, 60 s). The launch is detached: its own session on Unix, a detached process group on Windows, and its ownhook.log. The hook is told it is driven (LBM_HOOK_UI=1): it used to guess from its parent's path, which named the C# UI and does not name the agent.CLI
--fake-hookdrives an in-process hook that hooks nothing, on a private endpoint;--config-dir/--data-dirisolate the profiles (a load can write, e.g. the excluded-list top-up). Any run outside a real session should pass all three.Verified
--fake-hookwith a scratch store holding an enabled profile: KScreen discovery, the profile, 2 zones, Load+Run, then Loaded and Running. No real hook contacted, the real config untouched.--serve-fake-hook, hidden): launched, handed the layout, still running after the agent is killed, then told to quit. Nothing here starts the real hook; no process is left behind.Not yet: single instance and rotating logs, the Stop-by-kill fallback,
KScreenGapGuard(D7), inotify/uevents and logind sleep, the frontend API, tray and autostart, and the Windows endpoint and platform.🤖 Generated with Claude Code