Skip to content

feat(desktop): AO_KEEP_DAEMON to keep the daemon alive after the app closes#2231

Open
axisrow wants to merge 1 commit into
AgentWrapper:mainfrom
axisrow:feat/keep-daemon-alive-flag
Open

feat(desktop): AO_KEEP_DAEMON to keep the daemon alive after the app closes#2231
axisrow wants to merge 1 commit into
AgentWrapper:mainfrom
axisrow:feat/keep-daemon-alive-flag

Conversation

@axisrow

@axisrow axisrow commented Jun 27, 2026

Copy link
Copy Markdown

What

Adds an opt-in env var AO_KEEP_DAEMON: when set, the desktop app spawns its daemon without the OS-native supervisor link and skips the orphan-cleanup kill on exit, so the daemon persists across app quit and stops only on an explicit ao stop.

By default the app's daemon is tied to the window and self-stops shortly after the app quits — right for casual use, but long agent sessions get detached when the window closes. This gives power users a way to opt out.

Closes #2230.

Changes

  • daemon-owner.ts: keepDaemonAlive(env) helper; shouldLinkOnAttach honors it, so reopening the app never re-arms the link on a persistent daemon.
  • main.ts: gate establishSupervisorLink() on the spawn path and both attach paths; skip the process.on("exit") orphan-cleanup kill when the flag is set (this last one is essential — without it the fallback kill defeats the flag on quit).
  • README: AO_KEEP_DAEMON row in the config table.

Default behavior unchanged

With the flag unset, behavior is byte-for-byte the same: the daemon self-stops shortly after the app quits.

Intentional scope / omissions

  • Frontend-only, no backend change. The supervisor watchdog already arms only after the first client connects (headless ao start daemons never self-stop), so persistence is achieved purely by the app not holding the link — no daemon-side change needed.
  • Env var, not a Settings UI toggle (yet). Shipped as an env flag for a minimal, reviewable first step. A Global Settings toggle that writes the preference into the daemon env is a natural follow-up (the blank Settings page from feat(frontend): add blank Global Settings page #2218), left out here to keep the PR focused.
  • AO_OWNER=app left as-is. The persistent daemon is still tagged app-owned; shouldLinkOnAttach gates on the flag rather than the owner tag, so no owner-tag change is required.

Testing

  • Unit tests for keepDaemonAlive (truthy / empty / 0 / false / whitespace) and shouldLinkOnAttach persist cases.
  • Full frontend suite green: 354 passed (37 files).
  • npm run lint (backend go test ./... + golangci-lint) run for the touched repo — backend untouched, no new failures.
  • Manual e2e on a packaged build (macOS):
    • Default: open app → close window → daemon stops. ✓
    • AO_KEEP_DAEMON=1: open app → close window → daemon survives; ao stop stops it explicitly. ✓
    • App attaches to its own spawned daemon and shows projects (no stale state). ✓

🤖 Generated with Claude Code

@axisrow axisrow force-pushed the feat/keep-daemon-alive-flag branch 2 times, most recently from 1b18854 to 405be36 Compare June 28, 2026 01:01
@axisrow

axisrow commented Jun 28, 2026

Copy link
Copy Markdown
Author

Concrete motivating use-case: remote daemon in a container

Worth surfacing the strongest case for this flag — a headless / containerized daemon that must outlive any desktop client.

The supervisor watchdog arms only after the first client connects, then self-stops ~5s after the last client disconnects. That's exactly right for a window-tied desktop daemon. But consider running ao daemon inside a container (e.g. on a remote box) and attaching the desktop app over an SSH tunnel (loopback↔loopback, since the daemon is loopback-only by design):

  • The first desktop attach arms the in-container daemon's watchdog.
  • Closing that desktop window then drops the daemon ~5s later — killing every running agent session in the container, even though nobody asked it to stop.

AO_KEEP_DAEMON is what makes that container daemon stable: spawned/run without the app-lifetime link, it stops only on an explicit ao stop. The desktop app becomes a true client of a long-lived daemon rather than its owner.

I'm tracking the container-hosting side separately (in my own clihost repo, issue to bundle ao daemon + SSH tunnel), but the daemon-side knob that makes it safe is precisely this PR. So beyond "power users with long sessions," the persistent-daemon mode is a prerequisite for any detached / remote topology.

Default behavior is unchanged (flag unset → byte-for-byte the same self-stop). Happy to add a short note to the README row pointing at the remote/headless use-case if that's useful.

@axisrow axisrow force-pushed the feat/keep-daemon-alive-flag branch 3 times, most recently from ab0a1a7 to b7d178f Compare June 30, 2026 14:12
…closes

By default the desktop app's daemon is tied to the window via the OS-native
supervisor link and self-stops shortly after the app quits. That is right for
casual use, but long agent sessions are detached when the window closes.

Add an opt-in env var AO_KEEP_DAEMON: when set, the app spawns its daemon
without the supervisor link and skips the orphan-cleanup kill on exit, so the
daemon persists across app quit and stops only on an explicit `ao stop`.

- daemon-owner.ts: keepDaemonAlive(env) helper; shouldLinkOnAttach honors it so
  reopening the app never re-arms the link on a persistent daemon.
- main.ts: gate establishSupervisorLink() on the spawn path and both attach
  paths; skip the process.on("exit") fallback kill when the flag is set.
- Default behavior (flag unset) is byte-for-byte unchanged.
- Docs: AO_KEEP_DAEMON row in the README config table.

Closes AgentWrapper#2230

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@axisrow axisrow force-pushed the feat/keep-daemon-alive-flag branch from b7d178f to 596e506 Compare July 1, 2026 05:25
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.

Option to keep the daemon running after the desktop app closes

1 participant