Skip to content

feat(client): daemonLifecycle attach mode and ensureDaemon() - #3765

Merged
thymikee merged 1 commit into
feat/ensure-daemonfrom
feat/ensure-daemon-client
Oct 17, 2026
Merged

thymikee merged 1 commit into
feat/ensure-daemonfrom
feat/ensure-daemon-client

Conversation

@thymikee

@thymikee thymikee commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

Layer 2 of 3 for #3754 (umbrella #3747); based on #3764. The client half: who owns the daemon is now an explicit choice.

  • createAgentDeviceClient({ daemonLifecycle: 'attach' | 'managed' }); managed is the default and behaves as before (additive in 0.21.x).
  • attach connects to the registered daemon and never starts, replaces or kills one, including after a timeout. A missing daemon fails with details.reason: 'daemon-not-running', an incompatible one with 'daemon-incompatible' (both identities, hint names agent-device daemon stop).
  • ensureDaemon({ stateDir, onStart }) starts a missing daemon and replaces an incompatible one only through the quiesce request from feat(daemon): quiesce request that retires an idle daemon atomically #3764. A busy daemon fails daemon-busy, a daemon without the capability is never replaced, and a live daemon on another transport is quiesced, not killed. A daemon that times out on quiesce is force-retired only after repeated failed liveness probes.
  • A remote daemonBaseUrl connection never starts or retires a local daemon in either mode; ensureDaemon reads no remote env.
await ensureDaemon({ stateDir });
const client = createAgentDeviceClient({ stateDir, daemonLifecycle: 'attach' });

30 files incl. docs and changelog. The repo has no changeset tooling, so the CHANGELOG.md Unreleased entry is the changeset. Layer 3 adopts this in the CLI and MCP.

Validation

pnpm check:affected --run --head f21b0019dd: passed. New tests cover attach (missing, incompatible, unreachable, timeout), ensureDaemon (start, idle replace, busy, old daemon, transport, contention, unconfirmed retirement) and remote.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.32 MB 5.34 MB +11.2 kB
Package (unpacked) 5.32 MB 5.33 MB +11.2 kB
Package (download) 1.60 MB 1.61 MB +3.7 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.8 ms 28.1 ms +0.3 ms
CLI --help 81.3 ms 80.3 ms -1.0 ms

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-3765/

Built to branch gh-pages at 2026-10-17 06:38 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 26 files

You’re at about 92% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/daemon-client/daemon-client-ensure.ts Outdated
Comment thread src/daemon-client/daemon-client-quiesce.ts Outdated
Comment thread CHANGELOG.md Outdated
Comment thread website/docs/docs/client-api.md Outdated
Comment thread src/agent-device-client.ts
Comment thread src/cli/commands/proxy.ts Outdated
Comment thread packages/daemon-contracts/src/daemon-resolution.ts Outdated
Comment thread src/daemon-client/__tests__/daemon-client-explicit-lifecycle.test.ts Outdated
Comment thread src/daemon-client/daemon-client-ensure.ts
Comment thread website/docs/docs/client-api.md

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

You’re at about 92% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread website/docs/docs/client-api.md Outdated
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from 9f6aff4 to 4488553 Compare October 16, 2026 21:35
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch 2 times, most recently from 685c01a to c95c9c8 Compare October 16, 2026 22:28
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from c95c9c8 to 44eefce Compare October 16, 2026 23:05

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 14 files (changes from recent commits).

You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/daemon-client/__tests__/daemon-client-explicit-lifecycle.test.ts Outdated
Comment thread CHANGELOG.md Outdated
Comment thread src/daemon-client/__tests__/daemon-client-explicit-lifecycle.test.ts Outdated
Comment thread packages/contracts/src/client-connection.ts Outdated
Comment thread src/daemon-client/daemon-client-quiesce.ts Outdated
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from 44eefce to 5035bed Compare October 16, 2026 23:21
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch 2 times, most recently from 769eda3 to ddb6a5e Compare October 17, 2026 00:14
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from ddb6a5e to e9819db Compare October 17, 2026 00:38
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch 2 times, most recently from ad96e33 to 0c26950 Compare October 17, 2026 01:44
@thymikee

Copy link
Copy Markdown
Member Author

The reviewed commit 0c26950 has one problem that should change before merge. agent-device proxy now fails where it used to replace the daemon.

src/cli/commands/proxy.ts:32 used to call the managed ensureDaemon, which replaced an incompatible or socket-only daemon. It now calls ensureDaemonInfo. Three cases now fail: a daemon from the previous release with no quiesce capability (daemon-incompatible), an incompatible daemon that holds a session or lease (daemon-busy), and a live socket-only daemon that cannot be quiesced. The first case is the normal state right after an upgrade. A CLI user who runs agent-device proxy with the old daemon still running gets a refusal and must run daemon stop by hand. The PR body, the CHANGELOG line "managed stays the default and behaves as before", and remote-proxy.md do not mention this. Issue #3754 puts the CLI's move to idle-only replacement in layer 3, with its own changelog entry. The rule: no CLI surface changes ownership semantics in this additive layer. Please keep proxy.ts on ensureManagedDaemon and move the proxy switch into layer 3 with the CLI and MCP adoption. If it stays here, add a CHANGELOG entry and a remote-proxy.md note for the refusal.

Not blocking, and fine to take or leave: the rule "may this caller start or retire the daemon" is now encoded four times (daemonLifecycle, retireIncompatibleContender, the sendRequest timeoutRecovery, and handleRequestTimeout's retireUnresponsive); daemonStopCommand in daemon-client-timeout.ts duplicates stopHint and the inline strings in daemon-client-lifecycle.ts; the attach test in daemon-client-explicit-lifecycle.test.ts only checks the timeoutRecovery value on a spy, and every quiesce outcome comes from a scripted /rpc stub rather than the #3764 handler, so one sendToDaemon attach test with a wedged daemon and one ensureDaemon test that replaces a real spawned daemon would close that gap; the quiesce timeout in daemon-client-ensure.ts can rethrow the attach-worded hint "start one with ensureDaemon()" and needs its own hint; and the PR body says 26 files while the diff has 29.

Simplicity question: could daemonLifecycle, retireIncompatibleContender and timeoutRecovery/retireUnresponsive become one DaemonClientSettings field, authority: 'managed' | 'attach' | 'ensure', passed to sendRequest, with the contender rule, the timeout retire and the timeout hint all derived from it in one place? attachLocalDaemon and ensureOnce would then share one switch over observeLocalDaemon's decision. That drops about 30 lines and makes the invalid combinations impossible to write. It stays inside src/daemon-client, so no ADR is needed, and it is best done before layer 3 adds CLI and MCP callers that would otherwise set all four markers.

CI has no result on this head. All 9 checks that did not pass were cancelled as superseded runs on the stacked branch, and the diff touches each of those lanes, so please rerun them. I read the code and did not run the tests or check:affected, so the "passed" in the PR body is unverified by me. No test drives ensureDaemon against the real #3764 daemon_quiesce handler, so the client and daemon pairing rests on contract constants only. There are no conflicts. Before merge, keep proxy.ts on ensureManagedDaemon (or document its new idle-only replacement in the CHANGELOG and remote-proxy.md) and rerun the cancelled CI lanes on this head.

@thymikee
thymikee added this pull request to stack #3774 October 17, 2026 05:48
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from 0c26950 to d892ab3 Compare October 17, 2026 05:49
@thymikee

Copy link
Copy Markdown
Member Author

Thanks, the proxy regression was real. Fixed in d892ab3.

  • Blocking: agent-device proxy. It is back on the managed path (src/cli/commands/proxy.ts now differs from main only by the internal rename to ensureManagedDaemon), so it replaces a previous-release, incompatible or socket-only daemon exactly as before. ensureDaemonInfo is gone, and neither the CHANGELOG nor the PR text claims a proxy change. Moving proxy to idle-only replacement stays a separate CLI decision, not part of this additive layer.
  • Hints: one daemonStopCommand now backs every "stop it with agent-device daemon stop --state-dir ..." hint (lifecycle, timeout and error modules). The quiesce-timeout and incompatible hints now work for CLI and MCP users ("then retry"; SDK clients call ensureDaemon()).
  • Pairing with the real handler: the feat(daemon): quiesce request that retires an idle daemon atomically #3764 runtime test now builds its request with this PR's shared buildDaemonQuiesceRequest, so the client builder is exercised against the real daemon_quiesce handler.
  • PR body: file count corrected (30).
  • Not taken, with reasons: the authority enum. The four markers sit on different layers (client config, startup-race policy, sendRequest option, timeout hint) and merging them changes sendRequest's signature and the timeout module together; I would rather do that as its own refactor than in this stack. A sendToDaemon attach test with a wedged daemon: daemon-client-timeout-route.test.ts already drives a real wedged stand-in with a registered child through sendRequest, and the attach test pins the option sendToDaemon passes. An ensureDaemon test that replaces a real spawned daemon: client tests cannot boot the daemon runtime without importing daemon test support, so the pairing is covered by the shared builder above plus the stand-in daemons.

pnpm check:affected --run passes at d892ab3. CI is re-running; the cancelled lanes were superseded runs.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files (changes from recent commits).

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/daemon-client/daemon-lifecycle-errors.ts
Comment thread src/cli/commands/__tests__/proxy.test.ts
Comment thread website/docs/docs/client-api.md Outdated
Comment thread src/daemon-client/daemon-lifecycle-errors.ts Outdated
An attach client connects to the daemon registered in its state directory and never starts,
replaces or kills one, including after a request times out; it fails with daemon-not-running or
daemon-incompatible. ensureDaemon() starts a missing daemon and replaces an incompatible one only
through the daemon's own quiesce answer, refusing with daemon-busy while it holds work and never
replacing a daemon that predates the capability. managed stays the default and unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the feat/ensure-daemon-client branch from d892ab3 to f21b001 Compare October 17, 2026 06:28
@thymikee

Copy link
Copy Markdown
Member Author

The PR is ready at f21b001. The earlier findings from 0c26950 (#3765 (comment)) are fixed, and I found no new problems in the code.

Not blocking, and you can take or leave these: the daemon-not-running hint in https://github.com/callstack/agent-device/blob/f21b001/src/daemon-client/daemon-lifecycle-errors.ts#L62 tells CLI users to run agent-device devices without --state-dir, so with a non-default stateDir that command starts a daemon in the default directory and the attach fails again; the ensureDaemon half of the same hint already passes it. Also, no client test reads a census with startingUp: true, so the busy-hint branch at https://github.com/callstack/agent-device/blob/f21b001/src/daemon-client/daemon-lifecycle-errors.ts#L92 is never exercised; one ensureDaemon busy case asserting the hint text would cover it.

Of the four inline threads from the other review, these no longer apply, so you can resolve them: #3765 (comment) (the startup-busy hint now ends with the stop command), #3765 (comment) (the proxy test matches main apart from the rename, and proxy.ts passes the same transport settings as on main), #3765 (comment) (the docs now name streaming resumable uploads and lease-less host human-control holds as uncounted), and #3765 (comment) (the hint wording is fixed; the missing --state-dir is covered above). No other thread still applies.

I did not run any tests, so the "passes at d892ab3" claim is unverified on my side. No ensureDaemon test uses a real spawned daemon; the client and daemon pairing rests on the shared request builder and the stand-in daemons.

CI is pending after the rebase: 16 of 18 checks are queued or in progress, and none has finished with a failure. The diff touches the daemon-client lifecycle, the contracts, a daemon runtime test, the proxy command and its test, and the docs, so it overlaps Lint/Typecheck, Coverage, Integration, Smoke and command-docs-gate. There is no failure to attribute yet. There are no conflicts. The remaining step before merge is for CI on f21b001 to finish green.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
@thymikee

Copy link
Copy Markdown
Member Author

The review at f21b001 is still clean and the branch now merges cleanly, so I added the ready-for-human label.

@thymikee
thymikee merged commit 9fcf76d into main Oct 17, 2026
20 of 21 checks passed
@thymikee
thymikee deleted the feat/ensure-daemon-client branch October 17, 2026 08:44
thymikee added a commit that referenced this pull request Oct 17, 2026
…ment

* origin/main:
  feat(cli): ensure the daemon explicitly before attaching in the CLI and MCP (#3766)
  feat(client): daemonLifecycle attach mode and ensureDaemon() (#3765)
  feat(daemon): quiesce request that retires an idle daemon atomically (#3764)
  fix(daemon): answer a named missing session on the session, not the device (#3701)
  test: route test/ and scripts/ off the daemon's testing exports (#3733)
  refactor(plugins): make the plugin framework provider-independent (#3722)
  fix(apple): name an Xcode too old for its host macOS as a runner startup failure (#3739)

# Conflicts:
#	packages/daemon/src/server/http/daemon-runtime.ts
#	packages/daemon/src/sessions/handlers/session-appstate-device-runtime.ts
#	packages/daemon/test/nightly/concurrency-torture/real-scope-serialization.ts
#	src/__tests__/daemon-registration-owner.test.ts
#	src/agent-device-client.ts
#	src/cli.ts
#	src/cli/commands/proxy.ts
#	src/daemon-client/daemon-client-lifecycle.ts
#	src/daemon-client/daemon-client-metadata.ts
#	src/daemon-client/daemon-client.ts
#	test/integration/provider-scenarios/harness.ts
thymikee added a commit that referenced this pull request Oct 17, 2026
…refactor/daemon-runtime-no-process-globals

* origin/refactor/host-environment:
  refactor(daemon): read configuration from the request host instead of the ambient scope
  test: merge the duplicate test-kit import in the stale provider admission scenarios
  feat(cli): ensure the daemon explicitly before attaching in the CLI and MCP (#3766)
  feat(client): daemonLifecycle attach mode and ensureDaemon() (#3765)
  feat(daemon): quiesce request that retires an idle daemon atomically (#3764)
  fix(daemon): answer a named missing session on the session, not the device (#3701)
  test: route test/ and scripts/ off the daemon's testing exports (#3733)
  refactor(plugins): make the plugin framework provider-independent (#3722)
  fix(apple): name an Xcode too old for its host macOS as a runner startup failure (#3739)

# Conflicts:
#	packages/daemon/src/server/http/daemon-runtime.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant