Skip to content

feat(lbm-agent): the agent runs — runtime, system world, and detached hook supervision (D5) - #625

Merged
mgth merged 3 commits into
masterfrom
feat/lbm-agent-runtime
Sep 11, 2026
Merged

mgth merged 3 commits into
masterfrom
feat/lbm-agent-runtime

Conversation

@mgth

@mgth mgth commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Phase 3 of the v6 plan: lbm-agent runs. 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.md is 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.
  • Boot (Input::Boot, C#'s startup UpdateLayout): 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#'s LinuxLayoutFactory (sysfs plug signature, KWin/mutter output files).

Supervision (D5)

supervise::HookLauncher, the agent's DaemonProcessManager: on Unreachable it 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 own hook.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

lbm-agent [--fake-hook | --hook PATH] [--config-dir DIR] [--data-dir DIR]
lbm-agent --dump-displays

--fake-hook drives an in-process hook that hooks nothing, on a private endpoint; --config-dir/--data-dir isolate the profiles (a load can write, e.g. the excluded-list top-up). Any run outside a real session should pass all three.

Verified

  • On the maintainer's machine, --fake-hook with 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.
  • Tests: the loop against the fake hook (hand-over at boot, the first display change rebuilds then later ones re-hook, the user's Stop, a late hook and a replaced one), and supervision 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; 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

mgth and others added 3 commits September 11, 2026 21:57
- 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>
@mgth
mgth merged commit 1535f5e into master Sep 11, 2026
3 checks passed
@mgth
mgth deleted the feat/lbm-agent-runtime branch September 11, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant