feat(daemon): cross-platform supervisor + bicameral-mcp daemon CLI (Phase 2c-3)#508
Merged
Merged
Conversation
…hase 2c-3)
Fifth in the daemon-as-process arc — and the first PR where the daemon
actually runs as a separate OS-level process. Through 2c-2d the protocol
surface was real but the supervisor only managed the Runtime in-process.
This PR adds the subprocess spawn + signal handling + descriptor lifecycle
so MCP can launch a real detached daemon.
What ships:
- daemon/__main__.py — ``python -m daemon serve`` entry point. Bootstraps
asyncio + the MCP adapter shells (already from Phase 2b), installs
SIGTERM/SIGINT handlers, runs the loop until stopped, gracefully
shuts down the Supervisor.
- daemon/process.py — cross-platform spawn/stop/status helpers:
spawn() Popen with platform-conditional detach flags. POSIX uses
start_new_session=True; Windows uses DETACHED_PROCESS |
CREATE_NEW_PROCESS_GROUP. Same observable behavior on
every OS: detached child, parent exits, descriptor at
~/.bicameral/daemon.json names the PID + socket.
stop() SIGTERM → 10s grace → SIGKILL escalation. Idempotent
cleanup of stale descriptors (daemon crashed without
removing its file).
status() {"status": "running" | "stopped" | "stale", pid, socket}.
"stale" = descriptor exists but PID is dead.
is_alive() os.kill(pid, 0) probe (POSIX semantics; Windows treats
PermissionError as alive).
- cli/daemon_cli.py — argparse subcommands for ``bicameral-mcp daemon``:
start / stop / restart / status. Idempotent at the CLI layer — already-
running on start exits 0 with a warning; already-stopped on stop exits 0.
- server.py — wires the ``daemon`` subparser into the existing CLI.
- tests/test_daemon_lifecycle.py — 9 sociable tests. Each spawns a real
``python -m daemon serve`` subprocess against a tmp_path socket +
descriptor, exercises the wire boundary, then tears down. ~72s total
(~8s per test for the spawn). No mocks; the seam is the per-test temp
dir. Per Fowler's "measure before optimizing" — when this batch starts
costing measurable CI time, swap the fixture body for a pooled-warmed-
daemon implementation without touching the tests.
Out of scope (each gets its own focused PR):
- macOS LaunchAgent auto-start install (2c-3b)
- Linux systemd-user auto-start (2c-3c, when demand arrives)
- Windows Service install (2c-3d, when demand arrives)
Bicameral preflight unavailable this session (MCP server disconnected
mid-session); will validate against the ledger before 2c-4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
6 tasks
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.
Summary
Fifth in the daemon-as-process arc (plan) — and the first PR where the daemon actually runs as a separate OS-level process. Through 2c-2d the protocol surface was real but the
Supervisoronly managed theRuntimein-process. This PR adds the subprocess spawn, signal handling, descriptor lifecycle, and the CLI plumbing so MCP can launch a real detached daemon.End-to-end works locally (verified manually):
What ships
daemon/__main__.pypython -m daemon serveentry — bootstraps asyncio + MCP adapters, installs SIGTERM/SIGINT handlers, graceful shutdowndaemon/process.pyspawn()/stop()/status()/is_alive()— cross-platform viaPopenwith platform-conditional detach flagscli/daemon_cli.pybicameral-mcp daemonargparse:start/stop/restart/status. Idempotent at the CLI layerserver.pydaemonsubparser + dispatch into the existing CLItests/test_daemon_lifecycle.pypython -m daemon servesubprocess against atmp_pathsocket + descriptorCross-platform model
Same observable behavior on macOS / Linux / Windows; only the OS-detach flags differ:
stopuses SIGTERM on POSIX,CTRL_BREAK_EVENTon Windows (matches the new process group). 10s grace, then SIGKILL escalation.Test strategy
Per Fowler's 2026-05-22 advisory:
tmp_path.ObjectPoolof pre-warmed daemons. Tests don't change.Out of scope (separate PRs)
Until the platform-specific auto-start PRs land:
daemon startsurvives until reboot or explicitdaemon stop. For hosted-mode customers (the funnel-stage-1 focus) the hosting platform IS the supervisor and auto-start is irrelevant.Test plan
pytest tests/test_daemon_lifecycle.py— 9 passing (72s)ruff format --check . && ruff check .cleanmypy daemon/ cli/daemon_cli.pycleanKnown limitation
Bicameral preflight unavailable this session (MCP server disconnected mid-session). Will validate against the ledger before 2c-4.
Plan refs
thoughts/shared/plans/2026-05-22-daemon-as-process-arc.md🤖 Generated with Claude Code