feat(cli): run engram serve in the background as a daemon PR #2229
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
Research direction
Start with the serve command entry point and the code path that logs HTTP server did not bind and exits 0, since the exit-code fix is the smallest self-contained piece. The daemon, pidfile and service install work touches process lifecycle and the docs templates added in #279, so read those before designing anything. Done means a bind failure returns non-zero and the daemon verbs are agreed with maintainers first.
Written by the indexing model from the issue text.
Description
Existing Issue Search
- I searched existing issues and will add evidence to a canonical tracker when applicable.
🔍 Observed Consequence
Running engram serve as a persistent background daemon currently requires hand-authoring OS-specific supervisor units from the docs (launchd plist on macOS, systemd unit on Linux, Task Scheduler on Windows). Three concrete pain points, all reproduced today on a real macOS setup:
-
Silent staleness of manual units. The launchd template needs literal absolute paths (launchd does not expand
$HOME). After the install method changed (Homebrew/opt/homebrew/bin/engram→ a plain~/.local/bin/engraminstall), the previously installed plist kept pointing at the old binary path. The service was disabled and unloaded — the daemon silently stopped surviving reboots, with no diagnostic pointing at the cause. -
No CLI verbs for daemon control. Start/stop/status means remembering
launchctl/systemctlincantations and plist paths. There is noengram serve --status, no--stop, no way to ask engram itself whether a daemon is running. -
Bind failure exits 0. When the port is already taken,
servelogsHTTP server did not bind on 127.0.0.1:7437: an existing instance owns the portand exits with code 0. Under aKeepAlivesupervisor this produces a silent restart loop every throttle interval — the stderr log fills with bind failures while the supervisor's run counter just climbs. A supervisor cannot distinguish healthy from crash-looping, and users see nothing.
A related collision: MCP clients (e.g. the Pi adapter) spawn their own transient engram serve per session, so a supervised daemon and a session-owned instance race for the port with no defined ownership or reuse contract.
💡 Desired Outcome
CLI-native daemon support that works regardless of how the binary was installed:
engram serve --daemon # fork to background, write pidfile, print PID/port, exit 0
engram serve --status # running (pid N) / not running / unreachable
engram serve --stop # stop the daemon instance tracked by the pidfile
# or, as a superset:
engram service install # render + install the platform unit (launchd/systemd/schtasks) with the CURRENT binary path
Expected behavior:
--daemonbackgrounds the process (double-fork or equivalent), records a pidfile under the data dir (e.g.~/.engram/serve.pid), prints the PID and port, exits 0.- A second
serveagainst a live instance detects it (pidfile + port probe) and reportsalready running (pid N)with a non-zero exit code by default, so callers can decide to reuse or replace. - Port-in-use returns a non-zero exit code (today it is 0), keeping the clear message. Any supervisor (launchd KeepAlive, systemd Restart) can then react correctly instead of hot-looping.
service installrenders the existing docs templates with the real binary path and home directory, removing the literal-path fragility that made manual units go stale.
📦 Affected Area
CLI (commands, flags)
Release Context
not release-specific
🔄 Workarounds and Constraints
- Manual docs templates (added via #279): they work, but are copy-paste-heavy, go silently stale when the binary path changes (hit today: a launchd agent pointing at a removed Homebrew path), and every control verb is OS-specific shell knowledge.
- Homebrew
servicestanza (#1357): right direction for brew installs, but does not cover other install methods (e.g. a binary at~/.local/bin), and the CLI itself still has no daemon verbs. nohup engram serve &: no supervision, no restart on crash or reboot, dies with the session.
📎 Additional Context
Repro notes from today (macOS, arm64):
- A launchd agent with
KeepAlive=truerunning the currentserve: while another process held port 7437,engram serveexited 0 every ~10 seconds; stderr showed only repeatedHTTP server did not bind on 127.0.0.1:7437: an existing instance owns the portlines.launchctlkept respawning — an invisible crash-loop. - The stale-unit case: plist referencing
/opt/homebrew/bin/engramafter migrating to~/.local/bin/engram— service disabled and unloaded, nothing surfaces the mismatch.
Related: #279 (closed — docs templates + Local daemon: status line), #1357 (closed — Homebrew formula service integration). This request is complementary: CLI-level and install-agnostic, and it also gives programmatic clients a defined attach/reuse contract instead of racing the port.
- Dominant language
- Go
- Stars
- 7.1k
- Forks
- 729
- Avg merge
- 10h 46m
- Merged PRs (30d)
- 268
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Gentleman-Programming/engram
-
status:needs-review
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Gentleman-Programming/engram#2065 · 1 comment ·
Maintainers usually reply within 1 day
-
status:needs-review type:feature
Difficulty 4/5 3-5 days Newbie friendliness 22/100
Gentleman-Programming/engram#2203 · 1 comment ·
Maintainers usually reply within 1 day
-
feat(store): reduce isolated test fixture startup costPossibly taken @dnlrsls claimed this 4 days ago. Openstatus:approved type:feature
Gentleman-Programming/engram#2202 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
status:needs-review type:question
Difficulty 5/5 Over a week Newbie friendliness 20/100
Gentleman-Programming/engram#2201 · 3 comments ·
Maintainers usually reply within 1 day
-
status:needs-review type:feature
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Gentleman-Programming/engram#2197 ·
Maintainers usually reply within 1 day
All issues in Gentleman-Programming/engram
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
hyperledger/fabric-x-orderer#1409 ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
needs-area
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Azure/typespec-azure#5936 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
-
Fix mode unpins a reusable workflow call when the same repository is also used as a step actionOpen
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
github/gh-actions-lock#261 ·
Maintainers usually reply within 1 day