v0.8.0: always-on — systemd/launchd/Windows templates + /healthz#125
Conversation
…r v0.8
Always-On release: ship the four service templates + a stdlib /healthz
endpoint so `neuralmind watch` and `neuralmind serve` are first-class
production processes across reboots and crashes.
Changes:
- neuralmind/server.py: /healthz route registered before the
session-token gate so Docker HEALTHCHECK and systemd ExecStartPost
can probe without threading auth. Returns 200 + JSON
{"status": "ok", "version": neuralmind.__version__}.
- tests/test_server.py: 3 new tests — status+version shape, no-auth,
no-Set-Cookie.
- scripts/systemd/neuralmind-{watch,serve}.service: user-scope units.
serve unit's ExecStartPost polls /healthz for up to 5s.
- scripts/launchd/com.neuralmind.{watch,serve}.plist: macOS user
agents with RunAtLoad + KeepAlive + ThrottleInterval.
- docs/wiki/Scheduling-Guide.md: new "Always-on" PowerShell section
with two Register-ScheduledTask blocks + the /healthz verification
snippet.
- docs/use-cases/always-on.md: cross-platform walkthrough
(Linux/macOS/Windows + Docker HEALTHCHECK) with install + verify +
uninstall + troubleshooting.
- RELEASE_NOTES_v0.8.0.md: minimal release notes. Marketing rollout
(LinkedIn, NotebookLM, screencast, cross-doc callouts) deferred to
a follow-up per "ship code + minimum docs" scope.
Aider integration block deferred — current Aider stable has no
MCP-client support per docs check 2026-05-17.
Tests: 391 passing (3 new) + 4 environmental skips.
Closes part of #119 (the code + minimum docs); full close once the
marketing pass lands.
https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
NeuralMind self-benchmarkStatus: Phase 1 — Reduction on committed fixture
Phase 2 — Learning uplift
Note: uplift numbers on a 500-line fixture are intentionally modest — the point is to Assumptions
Per-model token reduction
Rows marked measured use the provider's real tokenizer. Rows marked Automated by |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f22efecff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| NoNewPrivileges=true | ||
| PrivateTmp=true | ||
| ProtectSystem=strict | ||
| ProtectHome=read-write |
There was a problem hiding this comment.
Use a valid ProtectHome value in systemd templates
ProtectHome=read-write is not a valid systemd value, so the hardening line is ignored; systemd-analyze verify scripts/systemd/neuralmind-watch.service (and the same check on neuralmind-serve.service) reports Failed to parse protect home value, ignoring: read-write. This leaves the unit with ProtectSystem=strict but without the intended home-directory override, which can break always-on operation when the service needs to write under the project directory (the default WorkingDirectory is in %h).
Useful? React with 👍 / 👎.
…write Codex P1 review on #125: ProtectHome only accepts true/false/read-only/tmpfs — "read-write" is silently ignored by systemd-analyze, leaving the unit with ProtectSystem=strict but no home-directory override. Means writes to %h/your-project/.neuralmind/ would fail at runtime under the intended hardening. Fix: drop the invalid ProtectHome line and add an explicit ReadWritePaths=%h allowlist that documents the actual intent (the unit needs to write the project's .neuralmind/ state). Same change applied symmetrically to neuralmind-serve.service. Verified locally: systemd-analyze verify on both files now reports only the expected "ExecStart not executable" template warning, no more ProtectHome parse errors. Review: #125 (Codex P1) https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
There was a problem hiding this comment.
Pull request overview
Adds the v0.8 always-on foundation for running NeuralMind’s watcher and graph-view server as background services, plus an unauthenticated /healthz endpoint for readiness/monitoring probes.
Changes:
- Adds
/healthztoneuralmind serveand tests its response/auth behavior. - Adds systemd and launchd service templates for
watchandserve. - Adds always-on user docs, Windows Task Scheduler examples, and v0.8.0 release notes.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
neuralmind/server.py |
Registers unauthenticated /healthz before auth gating. |
tests/test_server.py |
Adds server tests for /healthz. |
scripts/systemd/neuralmind-watch.service |
Adds Linux user service template for neuralmind watch. |
scripts/systemd/neuralmind-serve.service |
Adds Linux user service template for neuralmind serve. |
scripts/launchd/com.neuralmind.watch.plist |
Adds macOS launchd template for watch. |
scripts/launchd/com.neuralmind.serve.plist |
Adds macOS launchd template for serve. |
docs/use-cases/always-on.md |
Adds cross-platform always-on walkthrough. |
docs/wiki/Scheduling-Guide.md |
Adds Windows Task Scheduler always-on examples. |
RELEASE_NOTES_v0.8.0.md |
Adds v0.8.0 release notes. |
Comments suppressed due to low confidence (13)
scripts/systemd/neuralmind-watch.service:45
ProtectHome=read-writeis not a supportedProtectHomevalue on systemd; the directive will be rejected or ignored, so this template may not load with the intended hardening. Use a supported value and explicitly allow the project write path if needed.
# here so $HOME stays writable; the explicit ReadWritePaths is what
scripts/systemd/neuralmind-serve.service:45
ProtectHome=read-writeis not a supportedProtectHomevalue on systemd; the directive will be rejected or ignored, so this template may not load with the intended hardening. Use a supported value and explicitly allow the project write path if needed.
PrivateTmp=true
scripts/systemd/neuralmind-serve.service:34
- This service starts
neuralmind servewithout--no-browser, and the CLI opens a browser by default. As a daemon this can spawn a browser orxdg-openattempt on every boot/restart instead of running quietly in the background.
ExecStart=/usr/local/bin/neuralmind serve . --port 8765
scripts/launchd/com.neuralmind.serve.plist:40
- The launchd service runs
neuralmind servewithout--no-browser, but the CLI opens a browser by default. A user agent will therefore try to launch a browser on every login/restart instead of running quietly as a background service.
<string>serve</string>
<string>.</string>
<string>--port</string>
<string>8765</string>
docs/wiki/Scheduling-Guide.md:109
- Windows scheduled tasks have a default execution time limit (commonly 72 hours). Because this graph-view task is meant to run continuously, the settings should disable the time limit; otherwise
servecan be stopped after a few days despite being described as always-on.
-Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable `
-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1))
docs/use-cases/always-on.md:138
- Windows scheduled tasks have a default execution time limit (commonly 72 hours). Because this graph-view task is meant to run continuously, the settings should disable the time limit; otherwise
servecan be stopped after a few days despite being described as always-on.
-Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable `
-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1))
docs/use-cases/always-on.md:154
- The repo-root runtime image is based on
python:*-slimand does not installcurl, so this HEALTHCHECK will fail in the image the paragraph tells users to use unless they also add curl. Use a stdlib Python probe or document the extra package installation.
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:8765/healthz || exit 1
scripts/systemd/neuralmind-watch.service:24
- The instructions suggest using this as an instanced
@.service, but the unit does not use%i/%Ior an environment file inWorkingDirectoryorExecStart. Multiple instances would still point at the same placeholder project unless each copied unit is edited separately.
# This is a TEMPLATE — change WorkingDirectory and ExecStart to point
# at your project. Multiple projects? Copy the file with a suffix:
# cp neuralmind-watch.service ~/.config/systemd/user/neuralmind-watch@.service
# and use the @<project> instance form.
scripts/systemd/neuralmind-serve.service:35
neuralmind servebuilds the index before it starts listening, so this 5-secondExecStartPostcan fail and restart the unit on first run or larger projects even though the server is healthy once the build completes. The readiness probe needs a much longer startup window or should be removed from the template.
ExecStartPost=/bin/sh -c 'for i in $(seq 1 20); do curl -fsS http://127.0.0.1:8765/healthz >/dev/null && exit 0; sleep 0.25; done; exit 1'
scripts/systemd/neuralmind-serve.service:38
neuralmind serveonly handlesKeyboardInterrupt; it does not install the SIGTERM handler thatwatchuses. With this systemd stop signal, the process can terminate without running the server cleanup path for the watcher/event-log bridge, so the template's “clean shutdown” expectation is not actually met.
KillSignal=SIGTERM
RELEASE_NOTES_v0.8.0.md:119
- These macOS verification steps also assume a repository checkout. A user who just upgraded the package will not have
scripts/launchd/installed from the wheel, so the instructions need a download path or an explicit checkout prerequisite.
# macOS: install + verify launchd plists
cp scripts/launchd/com.neuralmind.{watch,serve}.plist ~/Library/LaunchAgents/
# edit WorkingDirectory + log paths, then:
docs/use-cases/always-on.md:29
- These commands only work from a source checkout. Since the package wheel does not install top-level
scripts/, PyPI/pipx users following the walkthrough need an explicit raw GitHub download command or a note to clone the repository first.
```bash
# 1. Copy the templates
mkdir -p ~/.config/systemd/user
cp scripts/systemd/neuralmind-watch.service ~/.config/systemd/user/
cp scripts/systemd/neuralmind-serve.service ~/.config/systemd/user/
docs/use-cases/always-on.md:76
- These copy commands only work from a source checkout. Since the package wheel does not install top-level
scripts/, PyPI/pipx users following the walkthrough need an explicit raw GitHub download command or a note to clone the repository first.
```bash
# 1. Copy the templates
cp scripts/launchd/com.neuralmind.watch.plist ~/Library/LaunchAgents/
cp scripts/launchd/com.neuralmind.serve.plist ~/Library/LaunchAgents/
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| by default. Use `--host 0.0.0.0` only if you understand the security | ||
| implications (see docs/SECURITY-GUIDE.md). |
| Register-ScheduledTask -TaskName "NeuralMind-Serve" ` | ||
| -Action (New-ScheduledTaskAction ` | ||
| -Execute "neuralmind" ` | ||
| -Argument "serve C:\path\to\your-project --port 8765") ` |
| Register-ScheduledTask -TaskName "NeuralMind-Serve" ` | ||
| -Action (New-ScheduledTaskAction ` | ||
| -Execute "neuralmind" ` | ||
| -Argument "serve C:\path\to\your-project --port 8765") ` |
| -Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable ` | ||
| -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)) |
| -Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable ` | ||
| -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)) |
| @@ -0,0 +1,52 @@ | |||
| # NeuralMind graph-view server — user-scope systemd unit. | |||
| # | |||
| # Keeps `neuralmind serve` reachable across reboots. The graph view is | |||
| Three platforms, same shape: pick the template, point it at your | ||
| project, enable it. The templates live in [`scripts/systemd/`](../../scripts/systemd/) | ||
| and [`scripts/launchd/`](../../scripts/launchd/) in this repo. |
| # Linux: install + verify systemd units | ||
| mkdir -p ~/.config/systemd/user | ||
| cp scripts/systemd/neuralmind-{watch,serve}.service ~/.config/systemd/user/ | ||
| # edit WorkingDirectory in both, then: | ||
| systemctl --user daemon-reload | ||
| systemctl --user enable --now neuralmind-watch neuralmind-serve | ||
| systemctl --user status neuralmind-watch neuralmind-serve | ||
|
|
||
| # macOS: install + verify launchd plists | ||
| cp scripts/launchd/com.neuralmind.{watch,serve}.plist ~/Library/LaunchAgents/ |
| - **`scripts/systemd/neuralmind-watch.service`** — user-scope unit for | ||
| the file watcher. Restart-on-failure, SIGTERM-clean shutdown, basic | ||
| hardening (`NoNewPrivileges`, `PrivateTmp`, `ProtectSystem=strict`). | ||
| - **`scripts/systemd/neuralmind-serve.service`** — same for the graph | ||
| view, plus an `ExecStartPost` that polls `/healthz` for up to 5 |
| Avoids polluting the cookie jar of monitoring clients.""" | ||
| fake_mind = SimpleNamespace(recent_queries=lambda n=20: []) | ||
| with _running_server(fake_mind) as base: | ||
| with urllib.request.urlopen(base + "/healthz", timeout=5) as resp: |
…ws time limit, healthcheck Bundle of fixes for PR #125 Copilot review: - scripts/launchd/com.neuralmind.serve.plist: rewrote the XML comment to remove the `--host` literal that contained `--`, which the XML spec forbids inside comments. xmllint now validates the file. Confirmed with `xmllint --noout`. - scripts/launchd/com.neuralmind.serve.plist + scripts/systemd/neuralmind-serve.service: add `--no-browser` to ExecStart / ProgramArguments so the daemon doesn't spawn a browser on every login/restart. - scripts/systemd/neuralmind-serve.service: bump ExecStartPost /healthz probe from 5s → 60s. `neuralmind serve` builds the embedding index before binding the socket; first runs on large projects can exceed 5s. - scripts/systemd/neuralmind-{watch,serve}.service: inline comments now mention `loginctl enable-linger "$USER"` for reboot survival without an active login session. - scripts/systemd/neuralmind-watch.service: drop the misleading "@.service instance form" hint — the template didn't actually use %i/%I. Replaced with explicit "copy once per project with a distinct name" guidance. - docs/use-cases/always-on.md + docs/wiki/Scheduling-Guide.md: add --no-browser to the Windows Register-ScheduledTask serve example and -ExecutionTimeLimit (New-TimeSpan -Seconds 0) to both watch + serve, disabling the default 72-hour cap. - docs/use-cases/always-on.md: fix the example /healthz version string (0.7.0 → 0.8.0), document how to retrieve the tokenized canvas URL from journalctl, fix the Scheduling-Guide anchor to match the v0.8 heading suffix, and replace the curl-based Docker HEALTHCHECK with a stdlib Python urllib probe (curl isn't in python:slim). - docs/use-cases/always-on.md: new "No checkout? Fetch the templates directly" section at the top with raw.githubusercontent.com URLs for pip/pipx/uv/Docker users — the wheel doesn't package scripts/. - RELEASE_NOTES_v0.8.0.md: soften "canvas always at 127.0.0.1:8765/" claim (the canvas requires the per-session token by default; document --no-auth as an opt-in trade-off), soften the 5s readiness claim, and update the Verification block to use curl-from-GitHub instead of cp-from-checkout. - tests/test_server.py: expand test_healthz_sets_no_cookie to cover the auth-enabled path so a regression that starts a cookie only when auth is configured can't slip through. Skipped: Copilot's SIGTERM claim — fact-checked against cli.py:438-439, which DOES install both SIGINT and SIGTERM handlers shared by watch and serve. The "clean shutdown" claim is accurate. Verified locally: 18 server tests passing, both plists xmllint-clean, both systemd units systemd-analyze-clean (only the expected template-placeholder ExecStart note remains). https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
PR #125 (v0.8 always-on) was merged with merge-commit title "v0.8.0: always-on…" instead of conventional-commit format (feat(serve): …). release-please reads merge-commit titles for non-squash merges and missed the feat: prefix, so it proposed v0.7.1 (PR #127, now closed) covering only the fix(ci) PAT patch from PR #126. The always-on code (/healthz, systemd/launchd templates, always-on.md walkthrough, RELEASE_NOTES_v0.8.0.md) is already on main. This empty commit's `Release-As: 0.8.0` footer overrides release-please's version proposal so the next release-please PR proposes v0.8.0 with the correct combined changelog (always-on + PAT fix). Process-fix going forward: merge commit titles for non-squash merges need conventional-commit format (this PR's own merge title is the example). Release-As: 0.8.0 https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Summary
v0.8 "Always-On" — first beat. Ships the four service templates + the
/healthzendpoint + the cross-platform walkthrough. Marketing rollout (LinkedIn, NotebookLM, screencast, cross-doc callouts) deferred per "ship code + minimum docs" scope.The user-facing claim:
neuralmind watchandneuralmind servenow survive reboots and crashes on Linux (systemd), macOS (launchd), and Windows (Task Scheduler). The synapse store accumulates 24/7; the graph view canvas is always athttp://127.0.0.1:8765/.Changes
neuralmind/server.py/healthzroute registered before the session-token gate so DockerHEALTHCHECKand systemdExecStartPostcan probe without auth. Returns 200 +{"status": "ok", "version": __version__}.tests/test_server.pyscripts/systemd/neuralmind-{watch,serve}.serviceExecStartPostpolls/healthzfor up to 5s.scripts/launchd/com.neuralmind.{watch,serve}.plistRunAtLoad+KeepAlive+ThrottleInterval.docs/use-cases/always-on.mdHEALTHCHECKsnippet.docs/wiki/Scheduling-Guide.mdRegister-ScheduledTaskblocks + the/healthzverification snippet.RELEASE_NOTES_v0.8.0.mdDeferred
Test plan
python -m pytest tests/— 391 passed (3 new), 4 environmental skipsruff checkclean on changed filesblack --checkclean on changed filessystemctl --user enable --now neuralmind-{watch,serve}works on a real Linux machinelaunchctl load -wworks on a real macOS machine (if you have one)Register-ScheduledTaskblocks land cleanly on a Windows hostOrder vs the rename PR (#124)
This PR doesn't depend on #124 merging first. It branched off the same main as #124 and only touches code + new scripts/docs. Merge either order. When #124 lands, this branch can rebase to pick up the v0.7.0 naming in shared docs (LinkedIn drafts, etc.), but nothing here breaks.
Versioning
Two
feat(...)commits (the /healthz + templates landing). Per the project's0.x feat = minorrule (PR #114), this should propose a clean v0.8.0 from release-please once it merges.Issues
Closes part of #119 — the code + minimum docs. Full close once the marketing pass lands as a v0.8.1 or rolled into v0.8.x.
https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Generated by Claude Code