Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions scripts/launchd/com.brainlayer.enrichment.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,20 @@
<string>com.brainlayer.enrichment</string>

<!-- Realtime Gemini enrichment LaunchAgent.
Runs once at login/load and then every 10 minutes. -->
Runs a supervised shell loop so the one-shot enrich command stays continuous. -->

<key>ProgramArguments</key>
<array>
<string>__BRAINLAYER_BIN__</string>
<string>enrich</string>
<string>--mode</string>
<string>realtime</string>
<string>--limit</string>
<string>50</string>
<string>/bin/zsh</string>
<string>-lc</string>
<string>cd "__BRAINLAYER_DIR__" || exit 1; while true; do "__BRAINLAYER_BIN__" enrich --mode realtime --limit 200000 --since-hours 87600; sleep 5; done</string>
</array>

<key>WorkingDirectory</key>
<string>__BRAINLAYER_DIR__</string>

<key>StartInterval</key>
<integer>600</integer>
<key>KeepAlive</key>
<true/>
Comment on lines +21 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid KeepAlive for one-shot enrich command

Switching this LaunchAgent to KeepAlive causes a restart loop because brainlayer enrich --mode realtime is a one-pass command (it returns after enrich_realtime(...) finishes, including the common no-candidates path). In that steady-state case the process exits quickly, launchd repeatedly respawns it, and macOS can classify it as “respawning too fast” and stop relaunching, which means realtime enrichment can silently stall until manual intervention while also churning logs/CPU. A timed trigger (or an internal sleep loop in the process) is needed for this command shape.

Useful? React with 👍 / 👎.


<key>StandardOutPath</key>
<string>__HOME__/Library/Logs/brainlayer-enrichment.log</string>
Expand All @@ -40,9 +37,9 @@
<key>BRAINLAYER_STALL_TIMEOUT</key>
<string>300</string>
<key>BRAINLAYER_ENRICH_RATE</key>
<string>50</string>
<string>15</string>
<key>BRAINLAYER_ENRICH_CONCURRENCY</key>
<string>10</string>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing BRAINLAYER_ENRICH_BACKEND=gemini environment variable in plist

High Severity

The PR description explicitly identifies BRAINLAYER_ENRICH_BACKEND being unset as one of the four configuration drift issues to fix ("unset instead of explicit gemini"), but the plist EnvironmentVariables dict never adds BRAINLAYER_ENRICH_BACKEND=gemini. Without it, _detect_default_backend() in enrichment.py will auto-detect mlx on Apple Silicon instead of gemini, meaning the service runs against the wrong backend entirely. The regression test test_enrichment_plist_matches_validated_flex_realtime_profile also omits any assertion for this variable, so the drift will go undetected.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 40c0388. Configure here.

<string>18</string>
<key>BRAINLAYER_GEMINI_SERVICE_TIER</key>
<string>flex</string>
<key>GOOGLE_API_KEY</key>
Expand Down
51 changes: 29 additions & 22 deletions tests/test_enrichment_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import json
import plistlib
import sqlite3
import sys
import threading
Expand Down Expand Up @@ -1160,36 +1161,42 @@ def test_enrichment_plist_uses_realtime_mode():
assert "realtime" in content


def test_enrichment_plist_has_start_interval():
def _load_enrichment_plist():
from pathlib import Path

plist_path = Path(__file__).parent.parent / "scripts" / "launchd" / "com.brainlayer.enrichment.plist"
content = plist_path.read_text()
assert "StartInterval" in content
assert "600" in content
return plistlib.loads(plist_path.read_bytes())


def test_enrichment_plist_invokes_cli_enrich_entrypoint():
from pathlib import Path
def test_enrichment_plist_uses_continuous_keepalive_shape():
plist = _load_enrichment_plist()

plist_path = Path(__file__).parent.parent / "scripts" / "launchd" / "com.brainlayer.enrichment.plist"
content = plist_path.read_text()
assert "__BRAINLAYER_BIN__" in content
assert "<string>enrich</string>" in content
assert "<string>realtime</string>" in content
assert "<string>50</string>" in content
assert plist["KeepAlive"] is True
assert "StartInterval" not in plist


def test_enrichment_plist_uses_low_priority_library_logs_and_flex_tier():
from pathlib import Path

plist_path = Path(__file__).parent.parent / "scripts" / "launchd" / "com.brainlayer.enrichment.plist"
content = plist_path.read_text()
assert "<key>Nice</key>" in content
assert "<integer>10</integer>" in content
assert "__HOME__/Library/Logs/brainlayer-enrichment.log" in content
assert "BRAINLAYER_GEMINI_SERVICE_TIER" in content
assert "<string>flex</string>" in content
def test_enrichment_plist_invokes_cli_enrich_entrypoint():
plist = _load_enrichment_plist()

args = plist["ProgramArguments"]
assert args[:2] == ["/bin/zsh", "-lc"]
command = args[2]
assert "__BRAINLAYER_BIN__" in command
assert "while true" in command
assert "enrich --mode realtime" in command
assert "--limit 200000" in command
assert "--since-hours 87600" in command


def test_enrichment_plist_matches_validated_flex_realtime_profile():
plist = _load_enrichment_plist()
env = plist["EnvironmentVariables"]

assert plist["Nice"] == 10
assert plist["StandardOutPath"] == "__HOME__/Library/Logs/brainlayer-enrichment.log"
assert env["BRAINLAYER_ENRICH_RATE"] == "15"
assert env["BRAINLAYER_ENRICH_CONCURRENCY"] == "18"
assert env["BRAINLAYER_GEMINI_SERVICE_TIER"] == "flex"


def test_launchd_installer_supports_enrichment_load_and_unload():
Expand Down
Loading